Install
$ agentstack add skill-dndungu-agent-skills-swiftui-craft ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
About
swiftui-craft — production SwiftUI craft rules
Consolidates the retired standalone skills swiftui-design-principles (v1.1.1, arjitj2, MIT), swiftui-design-skill (v1.0.0, wholiver, MIT), and ios-hig-design (v1.5.0, wondelai, MIT). Load when REVIEWING existing SwiftUI UI quality, when an approved design system is being implemented in a real app, or when auditing native-idiom / Apple HIG compliance. These are implementation-time rules: what separates an app that feels "right" and native from one where margins, spacing, text sizes, or platform conventions look "off". The HIG native-idiom framework (safe areas, Dynamic Type, Dark Mode, gestures/haptics, accessibility, SF Symbols, the 10/10 scoring rubric) lives in references/hig-overview.md; the topic router below points into it.
Does not do: greenfield design exploration, mockups, or design systems — that is /ios-design (direction → tokens → component cards → rendered screens). Widget plumbing (timelines, intents, entitlements, push reloads) → /widgetkit. Liquid Glass API work → /liquid-glass. Design-to-production validation pipelines → /design-fidelity.
Reconciliation with the mockup lane. Two rules below (semantic system colors, NavigationStack) are correct in a real app but wrong in the headless mockup lane (RENDERER.md fidelity limits). Resolution: mockups use Theme tokens and custom chrome; at implementation time each Theme token becomes an asset-catalog color (light+dark variants) or maps onto the closest semantic color, and custom mockup chrome becomes real NavigationStack / TabView chrome styled by the system. Apps WITHOUT a design system default to semantic colors outright.
Core philosophy: restraint over decoration — fewer colors, fewer font sizes, fewer spacing values, fewer words, used consistently. Attention is scarce — one clear headline and one compact supporting block beat repeated explanation scattered across title, subtitle, body, and footer.
1. Spacing: one grid, no arbitrary values
Allowed: 4 8 12 16 20 24 32 40 48. Values like 14, 18, 26, 34 create visual dissonance the eye registers even when the reader can't name it.
- Outer content padding: 16–20pt horizontal.
- Between major sections: 24–32pt vertical.
- Within grouped components: 4–12pt.
- Card/row internal padding: 12–16pt vertical, 16pt horizontal (never 4pt vertical).
2. Typography: hierarchy through weight, not just size
- ≤ 5 distinct font sizes per app, each with a named purpose. Lighter
weights at large sizes (elegant), regular/medium at small sizes.
- Example scale for a data app: hero 36–42
.light, secondary stat 20–24
.light, body 15 .regular, section header 11 .medium uppercase with tracking, caption 11–13 .regular.
- ONE font design everywhere — app AND widgets. Never
.monospacedin the
app but .rounded on the lock screen.
- Tracking: at most 2 values, only on uppercase labels (e.g. 1.5 for section
labels, 3 for toolbar titles). Never 3+ near-identical kerning values.
- Identifiers (years, IDs) must not locale-group:
Text(String(year))or
.number.grouping(.never) — never Text("\(year)") rendering "2,026".
- No
minimumScaleFactorhacks — fix the layout instead. - At most 2 weights per family per screen; one hierarchy channel — size OR
weight — per screen. Any runtime-updating number uses monospaced/tabular digits (no reflow jitter). Identity test: the app should be recognizable in a desaturated screenshot — typography carries the brand.
- Content text (body, labels, captions) uses semantic styles —
.font(.body),
.title, .caption — so Dynamic Type scales it; reserve the custom scale above for hero/display/stat text only. Test every layout at the largest Dynamic Type size (references/hig-typography.md for the color mapping).
3. Color: semantic first, tokens when a system exists
- No design system → semantic colors:
Color(.systemBackground),
.secondarySystemBackground, .separator, .primary, .secondary, .tertiary. They handle light/dark and accessibility for free.
- Design system →
Themetokens with explicit light+dark values (the
mockup lane already enforces this); wire them through the asset catalog.
- Never a ladder of hardcoded
Color.white.opacity(0.08 … 0.94)values.
If you need opacity at all, limit to 2–3 values with clear purposes (≈0.15 subtle strokes, ≈0.3 separators).
- Default blue tint nowhere — define an accent.
- Dark mode is designed, not inverted; semantic/adaptive colors tested in
both appearances. Dark carries measurable hue — no pure #000 background or #FFF text in either scheme; light/dark are one hue at two lightnesses.
- Hue budget: at most 4 saturated hues per screen, each with exactly one
documented meaning; ~70% of visible surface stays neutral — saturation lives in content and state, never chrome.
4. Component sizing: proportional, consistent strokes
- Progress rings: ~200pt in a main view, ~90pt in a small widget, thin
strokes (≈3pt). Never a 260pt ring with 8–9pt strokes dominating the screen.
- Background and foreground strokes of the same element use the SAME
lineWidth — 9 behind 8 reads as misalignment.
- Toggle rows: natural height from 12pt vertical padding, 15pt label, and
Toggle's built-in label — never .labelsHidden() plus a manual HStack, never .frame(height: 70).
- Tap targets ≥ 44×44pt, always.
5. Cards & grouped content: system patterns
- Corner radius 10pt for cards/groups (iOS system style); never 22pt+.
Radii nest concentrically (inner ~ half of outer) and vary by role — "everything 16pt-rounded on a gray field" is the canonical AI-slop tell.
- Background
Color(.secondarySystemBackground)(or the system's surface
token) — no decorative gradients or overlay borders on standard cards.
- Dividers: system
Divider().padding(.leading, 16)— never custom divider
structs.
6. Navigation & layout structure
NavigationStackwith a real toolbar — not a bareZStackwith a
manually placed title Text.
- Layout:
VStack/HStack/LazyVGrid; paged content viaScrollView+
.scrollTargetBehavior(.paging). Prefer containerRelativeFrame (iOS 17+) over GeometryReader; never UIScreen.main.bounds. Color, not UIColor/NSColor.
- Not one giant VStack — break into semantic sections with clear hierarchy.
- Native navigation model: tab bar for 2–5 primary destinations (never a
hamburger menu), push NavigationStack for drill-down, modal sheets for focused tasks (dismiss by swipe-down or explicit close). Back-button text is the previous screen's title, not "Back". Full model in references/hig-navigation.md.
- Respect safe areas: interactive content never under the notch, Dynamic
Island, or home indicator; .ignoresSafeArea() for backgrounds only. Design smallest-screen-first (375pt) and verify on iPhone SE through Pro Max.
7. WidgetKit
- Lock-screen circular/linear indicators:
Gaugewith.accessoryCircular
/ .linearCapacity — never manual circle drawing.
- Background:
.containerBackground(.fill.tertiary, for: .widget)— never a
hardcoded color.
- Support all relevant families (
accessoryCircular/Rectangular/Inline,
systemSmall/Medium/Large); medium and large share the same structural hierarchy (header / progress / footer) unless a hard size constraint forces otherwise.
- Explicit internal padding (≈12pt) on home-screen widgets to avoid clipping
at rounded edges.
- Memory budget ~30 MB: dense visualizations (365-dot grids) draw in one
Canvas pass, never hundreds of nested ZStacks in a LazyVGrid.
- Timeline refresh matches data granularity:
.after(midnight)for daily
data, ~15-min periodic for time-of-day fills — never minute-level for static daily data.
8. Interactive elements
Toggle(isOn:){label}with a single accent.tint— built-in label, not
hidden.
- Mutually exclusive options = ONE selected enum value rendered as a
checklist/picker, never N independent toggles that allow contradictory state.
- Changing numbers animate:
.contentTransition(.numericText()). - Motion carries state: no animation without a state/data trigger (the
remove-it test — if meaning survives removal, remove it). One signature animation per workflow; everything else ≤0.35s sharing at most two curves; no idle shimmer or bounce garnish.
- Density bimodality: a screen is either dense (≥3:1 type contrast, one
oversized anchor) or calm (few elements, generous margins) — medium density with medium contrast is the slop zone.
9. Interactive editors (crops, collages, canvases)
- Present from payload state:
.sheet(item: $activeRequest)— never
Bool + separately managed optional data that can race.
- ONE shared geometry model for live preview and export — preview and
render must not each invent their own pan/zoom/crop math. Revealed background on zoom-out is part of that model or it doesn't happen.
- Gestures (tap / long-press-drag / pinch) share one interaction state
machine with explicit priority — not scattered booleans.
- No-scroll editor screens budget height top-down through named regions
(header / canvas stage / settings / toolbar) with the math in one place.
- Custom headers: know whether the parent already respects the safe area —
never add safeAreaInsets.top reflexively (double-counting = dead space). Keep them compact: chrome, not a content section.
- Multi-mode editors show ONE active settings surface at a time; the canvas
stays visually dominant.
10. Shared data model (app + widgets)
One model, one calculation, used by both the app views and the widget TimelineProvider — never duplicated snapshot structs with re-derived date math. If the UI implies live progress, include time-of-day in the shared fraction.
11. Assets & placeholders
- SF Symbols or a real icon set — never emoji as functional icons, never
AI-drawn SVG clipart or CSS-silhouette illustrations.
- Can't source a real asset? Clean, labeled gray placeholder and ask the
user for materials. A rough honest layout beats a polished generic one.
12. Native iOS idiom (HIG) — accessibility, gestures, haptics
- Every interactive element carries an
.accessibilityLabel(noun: "Favorite");
.accessibilityValue for state, .accessibilityHint for effect (action: "Shares this item"); group with .accessibilityElement(children: .combine); hide decorative images with .accessibilityHidden(true). A VoiceOver user must be able to complete every task. Never convey meaning by color alone.
- Never override system gestures: swipe-from-edge (back), swipe-down (dismiss
modal), pull-down (refresh). Row swipe reveals actions; long-press shows context menus.
- Haptics are subtle and meaningful, never constant: impact for physical
actions, notification (.success/.warning/.error) for outcomes, selection for UI changes; .prepare() before triggering.
- Native controls over custom reimplementations (they carry accessibility and
haptics for free); system confirmations (.alert() / .confirmationDialog()), .swipeActions on rows, keyboard type paired with .textContentType.
Full HIG framework, scoring rubric (10/10), and per-topic depth in references/hig-overview.md.
Topic → reference router
| Topic | Reference file | |-------|----------------| | HIG framework, three pillars, 10/10 scoring, common mistakes, quick diagnostic | references/hig-overview.md | | Nav/tab bar dimensions, large-title behavior, split view | references/hig-navigation.md | | Semantic text hex values, Dark Mode text mapping | references/hig-typography.md | | Extra color-layering tokens, full WCAG contrast table | references/hig-colors-depth.md | | Button/list/section/picker/confirmation-dialog specifics | references/hig-components.md | | Keyboard types, input accessory views, hardware shortcuts | references/hig-keyboard-input.md | | VoiceOver patterns, pre-ship accessibility checklist | references/hig-accessibility.md | | App-icon sizes, squircle math, iOS 18 light/dark/tinted variants | references/hig-app-icons.md | | Reserved gestures, haptic recipes, animation timing/curves | references/hig-gestures.md | | Permission timing, priming screens, usage strings, denied recovery | references/hig-privacy-permissions.md | | Widget/App Clip/Live Activity/extension sizes + constraints | references/hig-widgets-extensions.md | | Siri/Shortcuts, Handoff, drag-and-drop, universal links, Spotlight | references/hig-system-integration.md |
13. Review checklist (run for review mode; score honestly)
Spacing on the grid · ≤5 font sizes, one design incl. widgets · semantic or token colors, no opacity ladders · equal stroke widths · 10pt card radius + system Divider · Toggle built-in labels · exclusive choices are one value · NavigationStack present · Gauge in lock-screen widgets · .containerBackground(.fill.tertiary) · widget families consistent + padded · Canvas for dense widget visuals · timeline matches granularity · no locale-grouped identifiers · tracking ≤2 values · no minimumScaleFactor · editors present from payload state · one geometry model preview+export · no double-counted safe areas · height budgeted centrally on fixed screens · one settings surface at a time · 44pt targets · dark mode designed · content text scales with Dynamic Type · interactive elements have accessibility labels and a VoiceOver-completable flow · native navigation (tab bar not hamburger, NavigationStack, modal sheets) · safe areas respected on SE→Pro Max · system gestures not overridden, haptics subtle · SF Symbols + correct app-icon shape · no banned slop patterns (~/.claude/skills/ios-design/DIRECTION.md §4) · one signature detail per screen.
Report review findings as file:line + rule + minimal fix, most severe first.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: dndungu
- Source: dndungu/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.