ax-design
npx machina-cli add skill Kasempiternal/axiom-v2/ax-design --openclawApple Design & SF Symbols
Quick Patterns
Background Color Decision
Is your app media-focused (photos, videos, music)?
+-- Yes --> permanent dark appearance (.preferredColorScheme(.dark))
+-- No --> system backgrounds (respect user Light/Dark preference)
systemBackground (main), systemGroupedBackground (Settings-style)
Label Hierarchy
Text("Title").foregroundStyle(.primary)
Text("Subtitle").foregroundStyle(.secondary)
Text("Detail").foregroundStyle(.tertiary)
Text("Disabled").foregroundStyle(.quaternary)
SF Symbol Rendering Mode Selection
Need depth from ONE color? --> Hierarchical
Need specific colors per layer? --> Palette
Want Apple's curated colors? --> Multicolor
Just need a tinted icon? --> Monochrome (default)
Symbol Effect Selection
User tapped something --> .bounce (discrete)
Draw attention to change --> .wiggle (discrete, iOS 18+)
Ongoing process / loading --> .pulse, .breathe, .variableColor (indefinite)
Rotation indicates progress --> .rotate (indefinite, iOS 18+)
Show/hide symbol --> .appear / .disappear (transition)
Swap between two symbols --> .replace (content transition)
Hand-drawn entry/exit --> .drawOn / .drawOff (iOS 26+)
Progress along path --> Variable Draw (iOS 26+)
Bounce on Tap
@State private var count = 0
Image(systemName: "arrow.down.circle")
.symbolEffect(.bounce, value: count)
Content Transition (Symbol Swap)
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
Decision Tree
What design question do you have?
|
+-- Background color?
| +-- Media-focused app --> permanent dark
| +-- Everything else --> systemBackground (adapts to light/dark)
| +-- Grouped content --> systemGroupedBackground
|
+-- Color choice?
| +-- UI elements --> semantic colors (label, secondaryLabel, etc.)
| +-- Brand color needed --> Color Set in asset catalog (light + dark + high contrast)
| +-- Never hardcode --> Color.black / Color.white
|
+-- Font weight?
| +-- Avoid: Ultralight, Thin, Light (legibility issues)
| +-- Headers: Semibold or Bold
| +-- Body: Regular or Medium
|
+-- SF Symbol rendering?
| +-- Single tint --> Monochrome (default)
| +-- Depth from one hue --> Hierarchical
| +-- Brand/status colors --> Palette (explicit per-layer)
| +-- Apple's curated colors --> Multicolor (not all symbols support it)
|
+-- Symbol animation?
| +-- Tap feedback --> Bounce (discrete, value: trigger)
| +-- Loading/waiting --> Pulse or Breathe (indefinite, isActive: bool)
| +-- WiFi/signal cycling --> Variable Color (indefinite)
| +-- Processing spinner --> Rotate (indefinite, iOS 18+)
| +-- Attention shake --> Wiggle (discrete, iOS 18+)
| +-- Swap symbols --> Replace content transition
| +-- Pen-drawn entry --> Draw On (iOS 26+)
|
+-- Light/Dark mode?
| +-- Always support both, never create app-specific toggle
| +-- Use semantic colors that adapt automatically
|
+-- Touch targets?
+-- iOS/iPadOS: 44x44 points minimum
+-- macOS: 20x20 points minimum
+-- Contrast: 4.5:1 minimum, 7:1 for small text
Anti-Patterns
HIG Violations
- Hardcoded
Color.black/.white-- use semantic colors (systemBackground, label) - App-specific dark mode toggle -- users expect systemwide preference honored
- Logo on every screen / navigation bar -- wastes space, violates content-first principle
- Ultralight/Thin/Light font weights -- legibility failures, accessibility risk
- Custom brand color for all text -- may fail 4.5:1 contrast requirement
- Splash screen with branding -- launch screens cannot include branding per HIG
SF Symbol Mistakes
.foregroundColor()with Multicolor mode -- overrides Apple's curated colors- Palette with only 1 color -- equivalent to Monochrome, provide colors for each layer
- Assuming all symbols support Multicolor -- unsupported fall back to Monochrome
- Hierarchical for status indicators where layers need distinct meaning -- use Palette
- Bounce effect for loading state -- one-shot, doesn't convey "ongoing"; use Pulse/Breathe
- Pulse for tap feedback -- too subtle; use Bounce
- Missing
.accessibilityLabelon symbol images -- VoiceOver reads raw symbol name - Using iOS 18+ effects without version check -- crashes on iOS 17
Rendering Mode + Effect Conflicts
- Multiple
.symbolEffect()on same view can conflict -- use single effect or combine with options .tintvs.foregroundStyleconfusion in UIKit -- tintColor for Mono/Hierarchical, paletteColors for Palette
Deep Patterns
Four Rendering Modes
Monochrome (default)
Image(systemName: "cloud.rain.fill")
.foregroundStyle(.blue)
// All layers same color. Use for: toolbar items, tab bar, matching text.
Hierarchical
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
// Layers at different opacities from single color. Most common for polished UI.
Palette
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .cyan)
// Explicit per-layer colors. Fewer colors than layers: last color reused.
Multicolor
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.multicolor)
// Apple's fixed curated colors. Cannot customize. Not all symbols support it.
Discrete Effects (Fire Once on Value Change)
// Bounce
.symbolEffect(.bounce, value: count)
.symbolEffect(.bounce.up, value: count)
// Wiggle (iOS 18+)
.symbolEffect(.wiggle, value: notificationCount)
.symbolEffect(.wiggle.forward, value: count) // RTL-aware
// Rotate (discrete, iOS 18+)
.symbolEffect(.rotate, value: refreshCount)
Indefinite Effects (Continuous While Active)
// Pulse -- subtle opacity pulse for waiting
.symbolEffect(.pulse, isActive: isConnecting)
// Variable Color -- layer cycling for signal/progress
.symbolEffect(.variableColor.iterative, isActive: isSearching)
.symbolEffect(.variableColor.cumulative, isActive: isLoading)
// Scale
.symbolEffect(.scale.up, isActive: isRecording)
// Breathe (iOS 18+) -- rhythmic scale
.symbolEffect(.breathe, isActive: isMonitoring)
// Rotate (indefinite, iOS 18+)
.symbolEffect(.rotate, isActive: isProcessing)
Effect Options
.symbolEffect(.bounce, options: .repeat(3), value: count)
.symbolEffect(.pulse, options: .speed(2.0), isActive: true)
.symbolEffect(.bounce, options: .repeat(5).speed(1.5), value: count)
Transitions (View Enter/Leave)
if showSymbol {
Image(systemName: "checkmark.circle.fill")
.transition(.symbolEffect(.appear))
}
// .appear.up, .appear.down, .disappear.up, .disappear.down
Content Transitions (Symbol Swap)
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
// Replace variants: .replace.downUp, .replace.upUp, .replace.offUp
// Magic Replace automatic for structurally similar pairs (star/star.fill)
Draw Animations (iOS 26+, SF Symbols 7)
Draw On / Draw Off
Image(systemName: "checkmark.circle")
.symbolEffect(.drawOn, isActive: isComplete)
Image(systemName: "star.fill")
.symbolEffect(.drawOff, isActive: isHidden)
Playback Modes
.symbolEffect(.drawOn.byLayer, isActive: show) // Staggered (default)
.symbolEffect(.drawOn.wholeSymbol, isActive: show) // All at once
.symbolEffect(.drawOn.individually, isActive: show) // Sequential
Variable Draw (Progress)
Image(systemName: "thermometer.high", variableValue: temperature)
.symbolVariableValueMode(.draw) // Path-based progress
Gradient Rendering (iOS 26+)
Image(systemName: "star.fill")
.symbolColorRenderingMode(.gradient)
.foregroundStyle(.red)
Version-Safe Effect Usage
struct BellEffect: ViewModifier {
let count: Int
func body(content: Content) -> some View {
if #available(iOS 18, *) {
content.symbolEffect(.wiggle, value: count)
} else {
content.symbolEffect(.bounce, value: count)
}
}
}
Custom Symbol Draw Annotation
- Open custom symbol in SF Symbols 7 app
- Select path layer, add guide points (start, end, corner, bidirectional)
- Minimum 2 guide points per path
- Test in Preview panel across weight variants
HIG Core Principles
- Clarity -- content paramount, interface defers, every element has purpose
- Consistency -- standard gestures, platform conventions, system colors/fonts
- Deference -- UI doesn't compete with content, subtle backgrounds, restrained branding
Pre-Ship Checklist
- Light Mode + Dark Mode tested
- Increased Contrast + Reduce Transparency tested
- Dynamic Type scales to 200% without truncation
- No light font weights (Regular minimum)
- Contrast ratio >= 4.5:1 (7:1 for small text)
- Touch targets >= 44x44 points
- Information conveyed by more than color alone
- VoiceOver labels on all interactive elements + symbols
- Reduce Motion respected (symbol effects auto-handled)
- RTL language support, no hardcoded strings in images
Platform Quick Tips
| Platform | Key Design Notes |
|---|---|
| iOS | Portrait-first, one-handed reach, bottom tab bar, swipe back |
| iPadOS | Sidebar-adaptable, split view, pointer, arbitrary windows (iOS 26+) |
| macOS | Menu bar commands, dense layouts, pointer-first, window chrome |
| watchOS | Glanceable, full-bleed, Digital Crown |
| tvOS | Focus-based, 10-foot distance, large targets |
| visionOS | Spatial layout, glass materials, comfortable depth |
Diagnostics
Symbol Effect Not Playing
- Check iOS version -- Bounce/Pulse/Scale: iOS 17+, Wiggle/Rotate/Breathe: iOS 18+, Draw: iOS 26+
- Check Reduce Motion setting -- most effects auto-suppressed when enabled
- Check trigger type -- discrete needs
value:that changes, indefinite needsisActive: true - Check symbol compatibility in SF Symbols app Animation inspector
- Check for conflicting
.symbolEffect()modifiers on same view
Wrong Symbol Colors
- Rendering mode not set -- add
.symbolRenderingMode(.hierarchical)or.palette .foregroundStylefrom parent overriding -- apply rendering mode directly on Image- Multicolor not supported by symbol -- falls back to Monochrome silently
Custom Symbol Weight Mismatch
Symbol weight follows applied .font() weight. Ensure custom symbol has 9 weight variants exported from SF Symbols app. Use .imageScale() for size, .font() for weight matching.
Draw Animation Not Working on Custom Symbol
Needs Draw annotation with >= 2 guide points per path. Guide points on stroked paths, not fills. Requires SF Symbols 7+ app for annotation.
Defending HIG Decisions
When stakeholders request HIG violations (logo everywhere, light fonts, custom dark toggle):
- Show Apple's specific guidance
- Demonstrate accessibility/rejection risk
- Offer HIG-compliant alternatives (brand tint color, accent color for actions, onboarding branding)
- If overruled, document decision and risks in writing
Related
ax-design-ref-- comprehensive HIG API reference, typography scales, Liquid Glass design specsax-swiftui-- SwiftUI patterns and decision guidanceax-swiftui-ref-- SwiftUI API reference including animation system
Source
git clone https://github.com/Kasempiternal/axiom-v2/blob/main/axiom-plugin/skills/ax-design/SKILL.mdView on GitHub Overview
This skill covers applying Apple Human Interface Guidelines and SF Symbols, including rendering modes, symbol effects/animations, and accessibility. It also addresses decision-making patterns and defending HIG choices in design reviews for consistent, accessible iOS interfaces.
How This Skill Works
Follow the provided decision tree and patterns to decide background color, label hierarchy, and SF Symbol rendering. Apply symbol effects such as bounce, wiggle, pulse, rotate, and draw animations (iOS 26) along with content transitions, ensuring accessibility and proper contrast throughout.
When to Use It
- Deciding background color strategy for light/dark/system backgrounds in a new app
- Choosing SF Symbol rendering mode to achieve depth or color accuracy
- Animating symbols for tap feedback, loading, or transitions
- Setting label hierarchy for title, subtitle, and detail text
- Defending HIG-based decisions during design reviews
Quick Start
- Step 1: Review Apple HIG and SF Symbols docs to understand background, color, and symbol guidelines
- Step 2: Define your background color strategy, font weights, and SF Symbol rendering mode
- Step 3: Implement symbol effects and transitions in SwiftUI (e.g., .symbolEffect, .contentTransition, .drawOn/.drawOff) and test accessibility
Best Practices
- Use semantic colors (systemBackground, label, secondaryLabel) instead of hardcoded Color.black/Color.white
- Honor the system light/dark mode; avoid app-specific toggles that bypass user preferences
- Choose SF Symbol rendering mode (Monochrome, Hierarchical, Palette, Multicolor) based on need for depth and color
- Apply symbol effects judiciously (bounce for tap, pulse/wiggle for attention, replace for content swaps)
- Ensure accessibility with proper contrast, Dynamic Type support, and meaningful accessibility labels
Example Use Cases
- Background color decision: a media-focused app uses permanent dark appearance to preserve media fidelity
- Label hierarchy: Text("Title").foregroundStyle(.primary) / Text("Subtitle").foregroundStyle(.secondary) / Text("Detail").foregroundStyle(.tertiary)
- SF Symbol rendering mode selection: choosing Monochrome for simple icons vs Hierarchical for depth or Palette for per-layer colors
- Symbol effects: .bounce on tap and .contentTransition(.symbolEffect(.replace)) for smooth transitions
- Draw animations: hand-drawn entry/exit using Draw On / Draw Off (iOS 26+) to emphasize state changes