CommunityResearch & Data Analysisgithub.com

hemal08ce094/taste-skill-swiftui

Anti-slop art direction for native SwiftUI apps (iOS, iPadOS, watchOS, macOS, visionOS), the mobile port of leonxlnx/taste-skill. A script deals researched directions (idea, composition, type, palette, shape, motion) instead of letting the model pick the category default; a native-first ladder (system component, styled system component, custom component only when the idea needs it) keeps the result at home on the platform; a Swift linter and a Simulator capture matrix check the rules; a critique loop judges the screenshots. Use whenever you design, build, redesign, restyle, polish or review an app screen, flow, onboarding, home, paywall, settings, widget or component in SwiftUI, and whenever the user says an app looks generic, templated, "AI-made", default or asks for taste, premium, award-level, feature-worthy design.

What is taste-skill-swiftui?

taste-skill-swiftui is a Claude Code agent skill that anti-slop art direction for native SwiftUI apps (iOS, iPadOS, watchOS, macOS, visionOS), the mobile port of leonxlnx/taste-skill. A script deals researched directions (idea, composition, type, palette, shape, motion) instead of letting the model pick the category default; a native-first ladder (system component, styled system component, custom component only when the idea needs it) keeps the result at home on the platform; a Swift linter and a Simulator capture matrix check the rules; a critique loop judges the screenshots. Use whenever you design, build, redesign, restyle, polish or review an app screen, flow, onboarding, home, paywall, settings, widget or component in SwiftUI, and whenever the user says an app looks generic, templated, "AI-made", default or asks for taste, premium, award-level, feature-worthy design.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/hemal08ce094/TasteSkillSwiftUI/tree/main/skill/taste-skill-swiftui

Installed? Explore more Research & Data Analysis skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

What does taste-skill-swiftui do?

An app screen built by a model looks like the average of every app screen the model has seen: a TabView of four tabs, a large title, a List of rounded cards each with an SF Symbol in a tinted square, a blue gradient "Get Started" button, an onboarding of three symbol pages, a paywall with a glow. Nothing in it is wrong and nothing in it was decided.

This skill replaces those defaults with decisions, in four ways:

  1. A script deals the directions, you choose among them. Asked to invent a look, a model picks the category default every time. scripts/draw.mjs deals three of twelve researched directions; you pick the one that carries the subject's idea, and it then deals type, palette, shape, motion and screen blueprints.
  2. The idea comes from the subject. The best apps are built on something taken from the thing itself (its output, its object, its ritual, its printed artefact), not on styling.
  3. Native first, custom on purpose. Every element climbs a ladder (below). Custom components are allowed, and encouraged when the idea needs one, but they must carry every native behaviour.
  4. You look at what you made. scripts/capture.sh photographs the app on a device matrix, light and dark, default and accessibility type; a juror's rubric judges the pictures; fix, look again.

Taking ornaments away is only half the job. A screen that lost its tinted squares, cards and gradients and gained nothing is thin. The other half is substance: type set larger, whole fields of colour, real pictures or drawn objects of this subject's world, every screen carrying its full content, and one thing the user can do that only this app has.

In the commands below <skill> is this skill's folder. Run them from the project root; working files land in .taste-skill/. The scripts need Node 18+. capture.sh needs Xcode and booted Simulators. When ~/.claude/bin/devlock exists, wrap every xcodebuild and simctl run in devlock run (parallel builds have exhausted the process limit before).

The native ladder

Climb only as far as the idea requires, and record every rung-3 component in the report.

  1. System component, system look. NavigationStack, TabView, List, Form, Toggle, Picker, Menu, ContentUnavailableView, .sheet with detents, ShareLink, Chart, ProgressView. Free: Dynamic Type, VoiceOver, Reduce Motion, RTL, Liquid Glass, keyboard and pointer. Use these for every control whose job is the platform's job.
  2. System component, styled. Same component, your tokens: ButtonStyle, LabelStyle, ToggleStyle, ListRowBackground, scrollContentBackground(.hidden), tint, fontDesign, fontWidth, containerShape. This is where most of a direction lives.
  3. Custom component. Only when no system component can carry the idea (a dial, a ruler, a ticket, a stage, a timeline spine). A custom component must ship with: Dynamic Type through @ScaledMetric/Font.custom(relativeTo:), a 44×44pt minimum hit area, accessibilityElement with label, value, traits and adjustable actions, a Reduce Motion path, haptics through sensoryFeedback, Dark Mode, RTL (layoutDirection), focus and pointer states on iPad/Mac, and a preview for every state. A control that needs a sentence of instructions is redesigned, not captioned.

Never rebuild what the platform owns: no hand-drawn tab bar, nav bar, switch, alert, sheet or keyboard accessory unless the idea cannot live without it. Never hand-roll a blur panel; Liquid Glass comes from system bars, toolbars and the system glass APIs on floating controls only.

Web to mobile: how the original rules map

Taste-skill (web)Here (native)
Page vs app screenAn app is almost always the second kind. The first-launch screen, onboarding and paywall are composed screens (follow the run). Lists, settings, forms and detail screens are working screens (follow references/screens.md).
HeroThe first screen after launch (or the Home tab). It answers "what is this, what do I do" in three seconds, in large type or a large object.
Headlines at most two lines at every widthTwo lines at the default size on iPhone SE-width and iPad, and wrapping (never truncating) at accessibility sizes.
Equal outer margins, no huddled sectionsUse the system layout margins (16/20pt); iPad and landscape get their own composition (split view, sidebar, two columns), never a centred phone column.
Real pictures floor (four)One real or hand-made visual per key screen. SF Symbols are functional glyphs, never the picture.
Scroll scenesscrollTransition, visualEffect, matchedGeometryEffect, navigationTransition(.zoom), KeyframeAnimator, PhaseAnimator, TimelineView.
The live thingThe wow moment: one interaction only this app has, with haptics, working one-handed.
inspect.mjs in a real browsercapture.sh in Simulators plus lint.mjs on Swift source.
Motion floorAt least six purposeful motion moments across first run and the core flow; each with a Reduce Motion fallback.
No glassNo hand-built blur cards. System glass on system chrome is fine.
No monospace.monospacedDigit() for aligned numbers only. Never design: .monospaced outside code.
No thin rulesList separators the system draws are fine. No decorative Divider, no 1pt outlined cards.
Footer = designed closeThe last row of a long screen, or the end state of a flow, is designed, not trailing space.

The run

Do these in order. Steps 1–5 take a few minutes and decide whether the app is any good.

1. Read the brief and the project

Find what is real: the subject, the audience, the one thing a user must do in the first minute, the platforms and deployment target, an existing design system or tokens, the assets that truly exist (photography, product shots, illustrations, footage, an image tool), and what must survive in a redesign (models, intents, widgets, purchases, localisations). Do not plan around assets you do not have. If the request is vague, decide and state your reading in one line; ask only when a missing fact blocks the work.

Inventory the subject: eight to twelve things that belong to this client and to no competitor. Its name and what it means; the numbers in the brief (minutes, grams, the price, the streak); the constraint it lives with (once a day, offline, one tap); its materials and colours; its rituals and hours; the printed things of its world (a ticket, a label, a receipt, a timetable, a passport stamp); its sound; its physical gesture (shake, blow, press and hold). The idea in step 4 comes from this list.

2. Write the argument

List the app as claims with their proof, one line per screen of the first-run and core flow:

Screen: claim -> what the user sees or does as the proof.

Delete any screen with no claim. This is how you avoid the stock sequence of splash, three onboarding pages, permission ask, paywall, tab bar of four. Onboarding earns its place only if it lets the user do the core thing once.

3. Draw the direction

node <skill>/scripts/draw.mjs --kind <kind> --brief "<one line about the subject>" \
  --screens <roles in the order of your argument> [--tone <tone>] [--have <assets>] \
  [--keep palette,type] [--platform ios|ipad|watch|mac|vision] --options 3

This deals three directions. Hold each against your inventory: which one can carry something only this client has? Choose that one, not the most familiar, and write one line on why. Then deal the full sheet:

node <skill>/scripts/draw.mjs <same arguments without --options> --direction <chosen id>
  • --kind: node <skill>/scripts/draw.mjs --list kinds (utility, health, finance, game, kids, social, media, productivity, lifestyle, creative, commerce, other).
  • --screens: roles from --list roles (onboarding, home, detail, create, results, settings, paywall, empty).
  • --tone: quiet, bold, warm, technical, playful, luxury, editorial. Only when the brief asks.
  • --have: assets that exist: photography, product-photo, illustration, video, 3d. Leave it out if none; the draw then avoids compositions that depend on them.
  • --keep palette,type: when the brand or design system already fixes them. The draw deals only composition, shape and motion; you map the system's values onto the roles.
  • --seed <n> makes a deal repeatable; the sheet prints the seed it used.

Adopt the sheet. Do not blend it with the layout you would have made anyway. Redraw one slot (--redraw type|palette|shape|motion|screen:<role>|direction) only for a reason you can state: it contradicts a brand constraint, needs an asset that does not exist, or cannot hold the real content. Never redraw to get something more comfortable. Record every redraw in the report. Reprint with --show.

Check the palette against the subject. A dealt palette is a set of proportions (one ground, one ink, one accent, optional fields) as much as colours. If it fights the subject's own material, redraw once, or keep the proportions and take colours from the subject, and say so.

4. Find the idea

The direction is a way of composing. The idea is what makes the app belong to this subject. Fill both blanks from the subject itself before building:

In this app, [something that belongs to the subject] becomes [the device the direction calls for].

Look in: its output (what it produces, shown as material), its object (its shape, in silhouette), its name (letters that hold a picture), its place, its ritual (a sequence the app performs), its artefact (a ticket, label, receipt), its opposite.

The competitor test: put the nearest real competitor in the sentence. If it still works, the idea belongs to the category. "Clean and modern" is not an idea.

The wow moment (the live thing). Give the app one interaction that is different for each use, made from the idea: blow into the mic and the candle gutters; set the dose and read the real half-life curve; fold the phone and the cassette plays. It uses real content, works one-handed with a thumb, has haptics through sensoryFeedback, a Reduce Motion path, and never blocks the task. It is one thing, not a playground. It is built in step 6, not added as motion at the end.

5. Write the words

Write the whole copy deck (plain text) before views, following references/copy.md, and put every string in the String Catalog (Localizable.xcstrings) from the first commit. Every title fits two lines at the default size; one primary action per screen, named for what it does (never "Get Started", "Continue" with nothing after it, "Learn More"); empty, error and permission copy written now, not later. Proof is never invented: no fake reviews, user counts, awards or press.

6. Build it, still

Build with motion switched off in your head. Order of work:

  1. Tokens. Palette/Theme from the sheet as asset-catalog colours with light and dark variants (or a ShapeStyle token enum), a Typography enum, a Metrics enum (spacing, radius, one shape rule). Everything downstream references tokens only. No Color(red:), no .font(.system(size: 17)) in views.
  2. The first screen, exactly as its blueprint describes it, at SE-width, standard, Pro Max and iPad. Get the big thing big and the small things small before anything else exists.
  3. Each screen from its blueprint with this project's content. Neighbouring screens must not share a layout. Carry the idea into every screen, settings, paywall and empty states included; the commonest failure is a distinctive Home followed by stock Forms.
  4. Imagery, by preference: the project's own; generated (if you have an image tool); real pictures of the subject's world from open collections with licences recorded (references/imagery.md); drawn in code (Shape, Canvas, Path) as one consistent flat graphic language in the palette; type and colour alone. SF Symbols only as functional glyphs.
  5. The wow moment from step 4, in the screen where it proves the claim.
  6. Fullness. Look at each screen as a poster: one dominant thing and the content it carries. A screen that is mostly empty ground is underbuilt. Running text in the full ink at body or larger.
  7. The frame. One margin system. Every screen has a title (navigation title or a large display title) and every list section a header that names it.
  8. States and semantics. All of them: loading, empty, error, offline, permission denied, success, disabled, first run, long content, accessibility sizes, RTL. Real Buttons, visible focus, labels, accessibilityHint only where the label cannot carry it, VoiceOver order, Reduce Motion and Increase Contrast respected.
  9. Platform baseline (from the global rules): String Catalog with en-US, en-GB, en-AU, en-CA and es-MX; requestReview after a positive moment only, gated by counter and days since install, once per version; haptics; Dark and Light; App Intents where the app has a core action.

Read references/type.md, colour.md, layout.md and components.md when you need the detail behind a decision; the laws below are the short form.

7. Gate

node <skill>/scripts/lint.mjs <Swift files or folder>
<skill>/scripts/capture.sh <bundle id> .taste-skill/capture   # booted Simulators

lint.mjs scans Swift source for banned patterns, stock phrases and missing baselines (String Catalog, Reduce Motion, haptics). capture.sh launches the installed app on each booted Simulator in light and dark and at default and accessibility sizes, and saves the screenshots. Fix every FAIL; treat each WARN as wrong unless you can defend it to a designer in one sentence. Re-run until no FAIL. A line may opt out with a trailing // taste:allow <rule> and a reason; the report lists every opt-out.

Inspect what ships: run the capture on a Release build of the real scheme, not only SwiftUI previews. Previews do not show system chrome, Liquid Glass, safe areas on real devices or the keyboard.

8. Look

Passing the gate means nothing banned was measured. Open the screenshots (Read tool) and critique them with references/critique.md: fourteen passes, the weighted score, the caps. Write the critique down in its format, fix the top three problems, capture again, look again. Three rounds at most. Judge only the pictures.

9. Move

Build the motion layer (references/motion.md). It is required: six purposeful moments minimum.

  1. The signature move named on the sheet, done well.
  2. Entrance for the parts of a screen, heading, then text, then picture, staggered 50–80ms, rising 12–24pt with opacity, once, never replayed on every onAppear re-entry.
  3. Feedback on every control: a pressed state through ButtonStyle, sensoryFeedback, and contentTransition(.numericText()) on changing figures.
  4. One moving picture or one scroll-linked move on the key visual.
  5. Transitions between screens that keep identity (navigationTransition(.zoom), matchedGeometryEffect).

Springs, not bounce; transform and opacity, not layout; every move has a reduceMotion path that keeps the content and drops the travel. Capture once more and check nothing failed to appear.

10. Report

Do not report until all are true:

  • lint.mjs shows 0 FAIL on the final code.
  • You opened every screenshot of the last capture, not only the first screen.
  • A stranger glancing at the first screen for five seconds can say what the app is and what to do.
  • Every screen has a title; margins hold at SE-width and iPad; no screen is a stretched phone layout.
  • The critique of your last round is written down in the shape of critique.md §4.
  • String Catalog covers every new string in all five locales.

Tell the user, briefly: the direction (name and seed) and redraws with reasons; the idea sentence and the wow moment; every rung-3 custom component and why; the last critique scores and what falls short; every found picture with title, creator, licence and source; sample content to swap; every taste:allow; and exactly which devices, appearances and sizes were and were not verified. Do not claim a check you did not run.

House rules

These hold whatever the direction. lint.mjs checks what source can show; the rest you check by looking.

RuleInstead
Titles of two lines or fewer at the default size; they wrap, never truncate, at accessibility sizes.Cut words. Widen the measure. Size last.
A clear title on every screen; the first screen says what the app is and what to do.Name plus what it does, in the large title or display type.
No eyebrows. No small label above a heading, no pill badge ("New", "Pro") above a title.Fold it into the title or first sentence, or drop it.
No tiny uppercase tracked captions scattered over a screen.Sentence case at footnote or larger, placed with its content. (Native grouped-list headers are the one place the system uppercases; keep them.)
No monospace outside code.The text face; .monospacedDigit() where numbers align.
No index numbering (01 / 02 / 03, "Step 2 of 3" chrome).Order by position, scale, or the words.
No decorative rules. No hairline dividers between cards, no 1pt outlined cards, crop marks, dashed guides.Space, a tone step, a filled shape. System list separators stay.
No gradients. Not on backgrounds, buttons, text, rings or icons. No MeshGradient as decoration.Flat colour or a real photograph. A neutral scrim under text on a photo is the one exception.
No shadows on things at rest. No glows.A tone step between surfaces. Shadows only on what truly floats.
No hand-rolled glass. No blur cards, floating pill bars.System bars and system glass APIs on floating controls.
No icon-topped feature cards, no rows of identical cards, no icons in tinted rounded squares.The dealt screen blueprint.
No emoji as icons or bullets.Words, SF Symbols as functional glyphs, a real picture.
No assistant palette. No violet/indigo, no cream plus terracotta, no neon on near-black with a glow, no default system-blue everywhere.The dealt palette.
No styled accent word in a title (italic, coloured, underlined).One face, one colour.
Two type families at most, usually the system face in one or two designs.The dealt type recipe.
One shape rule decided once: sharp, capsule, or the system's concentric continuous corner.The dealt shape. No second radius "because it looked nice".
Hard-coded sizes, colours and strings stay out of views. No UIScreen.main.bounds, no device widths, no Color(red:), no sentences in code.Tokens, ScaledMetric, layout containers, String Catalog.
No bounce, no infinite loops that mean nothing, no hover lift on every card.One signature move, restrained springs.

Why these: the web study behind taste-skill found these devices on weak sites far more than on strong ones. The mobile equivalents (tinted-icon cards, gradient buttons, default blue, stock onboarding) are what generators produce for apps. references/tells.md keeps the dated registry.

Removing ornament leaves space. Never fill it with another ornament. Fill it with scale, with full content, with a picture made for this subject, or with a flat field of colour. Leave it empty on purpose once per screen at most.

Laws of the craft

Type

  • Two or three sizes do the visible work: one very large, one body, one small. Headline-to-body ratio near 3 or more on composed screens; calmer on working screens.
  • Everything scales: @ScaledMetric(relativeTo:) for custom sizes, text styles for the rest. Check accessibility3; layouts reflow (ViewThatFits, AnyLayout, dynamicTypeSize switches), they do not clip.
  • Display type: tracking about -0.02 to -0.04em (.tracking), tight leading (.leading(.tight) on the font, or a small negative .lineSpacing at display sizes only; recheck at accessibility sizes).
  • Five distinct sizes on a screen is the ceiling. Left-aligned by default; centre one moment per screen.

Space

  • One margin system (system layout margins, 16/20pt). Large things run to the edges; text blocks share two or three left edges.
  • Section gaps clearly larger than any gap inside a section. Spacing on a 4pt grid through tokens.
  • Respect safe areas and the home indicator; the primary action sits in the thumb zone (bottom), not the top-right toolbar, unless it is the platform convention.
  • iPad, landscape and Stage Manager sizes get their own composition (sidebar, two columns, detail alongside); a stretched or centred phone column fails.

Colour

  • One ground, one ink, one accent. The accent marks the primary action and perhaps one more thing.
  • Running text in full ink. Secondary ink at 4.5:1 or better; check with scripts/check-palettes.mjs or antislop-human/contrast-check.py.
  • Surfaces separate by a small tone step. A screen may change the whole field colour; nothing fades into anything. Dark mode is designed (its own values), not inverted.
  • Honour Increase Contrast and Differentiate Without Colour (never state by hue alone).

Imagery

  • One dominant visual beats six small ones. Same crop ratios, same treatment, shapes from the shape rule. No image is better than an icon-in-a-circle placeholder.
  • Every found picture is downloaded into the project, credited in Settings, and listed in the report.
  • Nothing lies on top of a title (text-over-photo needs a measured scrim and a contrast check).

Components

  • Buttons: one primary per screen, flat, one fill colour, shape decided once. Many strong screens have one button and no secondary.
  • Navigation: native. The tab bar has three to five tabs named by nouns; no custom bar chrome.
  • Sheets use detents and a drag indicator; destructive actions confirm through confirmationDialog.
  • The last row of a flow is designed: success states are earned, specific, and short.

Special cases

An existing design system or brand. Pass --keep palette,type, map its values to the roles, take only composition from the draw. If the brand itself requires a banned device (logo in a gradient), keep the brand asset, say so, and add // taste:allow <rule> for that line only.

Redesign, restyle or review of an existing app.

  1. Record the before: capture it into .taste-skill/before and lint its source. Read the code: the navigation structure, state model, localisation, what must survive (intents, widgets, purchases).
  2. A review stops here. Critique the before screenshots with critique.md, list findings with the linter's measurements, and name the three changes that would move the app most.
  3. Keep the architecture, the content and what works. A restyle keeps layout and takes type, colour and components from a dealt direction; a redesign deals a full direction and rebuilds screens from its blueprints inside the existing views.
  4. Order of work, biggest effect for least risk: type, colour, the frame (margins, rhythm, titles), states, then stock components (tinted-icon cards, gradient buttons, stock onboarding).
  5. Prove it: capture into .taste-skill/after, compare every device and appearance, run every flow that worked before, report before and after measurements.

A component or a single screen, not an app. Skip the draw unless there is no direction to follow. Match the surroundings, apply the house rules, climb the ladder, still capture the result.

watchOS, macOS, visionOS. Same run; --platform changes the blueprints. Watch: one glance, one action, Digital Crown as the live thing, no scrolling hero. Mac: sidebar plus content, menus, keyboard shortcuts, resizable windows. Vision: depth through ornaments and volumes, no flat phone UI floating in space. Watch apps need an iOS companion screen (rating prompt lives there).

No Simulator, no Xcode. Run lint.mjs, critique whatever screenshots or previews exist, and say in the report that the capture could not run.

Files

  • references/tells.md — dated registry of generated-app tells.
  • references/type.md — type recipes, SwiftUI snippets, Dynamic Type rules.
  • references/colour.md — palette proportions, asset-catalog setup, contrast, dark mode.
  • references/layout.md — margins, safe areas, size classes, iPad/Mac/Vision composition.
  • references/components.md — web-to-native component mapping and custom-component checklist.
  • references/screens.md — working screens: lists, forms, settings, charts, states.
  • references/motion.md — springs, transitions, haptics, Reduce Motion, tested recipes.
  • references/imagery.md — sourcing, drawing in code, licences, treatments.
  • references/copy.md — argument, titles, buttons, empty states, localisation.
  • references/critique.md — how to judge the capture, the score, the caps.
  • library/ — directions.json, palettes.json, type.json, shapes.json, motions.json, screens.json consumed by draw.mjs.
  • scripts/draw.mjs, lint.mjs, capture.sh, check-palettes.mjs.

Related Skills