Use when implementing SF Symbols rendering modes, symbol effects, animations, custom symbols, or troubleshooting symbol appearance - covers the full symbol effects system from iOS 17 through SF Symbols 7 Draw animations in iOS 26
Implements SF Symbols rendering modes, effects, animations, and custom symbols for iOS development.
npx claudepluginhub charleswiltgen/axiomThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Use when:
axiom-sf-symbols-ref for complete API reference with all modifiers, UIKit equivalents, and platform availability matrixaxiom-swiftui-animation-ref for general SwiftUI animation (not symbol-specific)axiom-hig-ref for broader icon design guidelinesThe skill covers rendering mode selection — Hierarchical for depth from a single color, Palette for explicit per-layer colors
The skill covers effect selection: Bounce for tap feedback, Breathe/Pulse for in-progress, Replace with content transition for completion
The skill covers Draw On/Off implementation, playback modes, iOS 26 requirements, and common troubleshooting
The skill covers custom symbol authoring workflow, template layers, Draw annotation with guide points
SF Symbols support 4 rendering modes. The right choice depends on your design intent.
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)
The default mode. Every layer renders in the same color (your foregroundStyle).
Image(systemName: "cloud.rain.fill")
.foregroundStyle(.blue)
// All layers are blue
When to use: Simple tinted icons, matching text color, toolbar items, tab bar items.
Renders layers at different opacities derived from a single color. Primary layers are fully opaque; secondary and tertiary layers get progressively more transparent. Creates depth without specifying multiple colors.
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
// Cloud is full blue, rain drops are lighter blue
When to use: When you want visual depth but still want the icon to feel cohesive with a single hue. Most common choice for polished UI.
Each layer gets an explicit color. Unlike Hierarchical, no automatic opacity derivation — you control each layer's color directly.
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .cyan)
// Cloud is blue, rain drops are cyan
When to use: Branded icons, status indicators where specific colors carry meaning, designs requiring exact color control.
Gotcha: If you provide fewer colors than layers, extra layers reuse the last color. If the symbol has 3 layers and you provide 2 colors, the third layer uses the second color.
Uses Apple's predefined color scheme for each symbol. Colors are fixed — you cannot customize them.
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.multicolor)
// Cloud is white, rain drops are blue (Apple's design)
When to use: Weather indicators, file type icons, or anywhere Apple's curated design intent matches your needs. Not all symbols support Multicolor — unsupported symbols fall back to Monochrome.
| Mistake | Impact | Fix |
|---|---|---|
Using .foregroundColor() with Multicolor | Overrides Apple's colors | Remove foreground color modifier |
| Setting Palette with only 1 color | Looks like Monochrome | Provide colors for each layer |
| Assuming all symbols support Multicolor | Fallback to Monochrome | Check in SF Symbols app first |
| Using Hierarchical when layers need distinct meanings | Colors don't carry semantic intent | Use Palette instead |
Symbol effects bring SF Symbols to life with motion. Every effect falls into one of four behavioral categories.
| Category | Trigger | Duration | Use Case |
|---|---|---|---|
| Discrete | Value change | One-shot | Tap feedback, event notification |
| Indefinite | isActive bool | Continuous until stopped | Loading states, ongoing processes |
| Transition | View insert/remove | One-shot | Appear/disappear with style |
| Content Transition | Symbol swap | One-shot | Replacing one symbol with another |
User tapped something → Bounce (discrete)
Something changed, draw attention → Wiggle (discrete, iOS 18+)
Ongoing process/loading → Pulse, Breathe, or Variable Color (indefinite)
Rotation indicates progress → Rotate (indefinite, iOS 18+)
Show/hide symbol → Appear/Disappear (transition)
Swap between two symbols → Replace (content transition)
Symbol enters with hand-drawn style → Draw On (iOS 26+)
Symbol exits with hand-drawn style → Draw Off (iOS 26+)
Progress indicator along path → Variable Draw (iOS 26+)
Scale up/down for emphasis → Scale (indefinite)
Fire once when a value changes. The symbol performs the animation and returns to its resting state.
The most common discrete effect. A brief, springy animation.
@State private var downloadCount = 0
Image(systemName: "arrow.down.circle")
.symbolEffect(.bounce, value: downloadCount)
The animation triggers each time downloadCount changes.
Directional options: .bounce.up, .bounce.down
A horizontal shake that draws attention to the symbol.
Image(systemName: "bell.fill")
.symbolEffect(.wiggle, value: notificationCount)
Directional options: .wiggle.left, .wiggle.right, .wiggle.forward, .wiggle.backward
.forward and .backward respect reading direction — use these for RTL support.
A single rotation when triggered by value change.
Image(systemName: "arrow.trianglehead.2.clockwise")
.symbolEffect(.rotate, value: refreshCount)
Options: .rotate.clockwise, .rotate.counterClockwise
By Layer: Some symbols rotate only specific layers (e.g., fan blades spin but the housing stays fixed). Use .rotate.byLayer to activate this.
Run continuously while isActive is true. Stop when isActive becomes false.
A subtle opacity pulse. Good for "waiting" states.
Image(systemName: "network")
.symbolEffect(.pulse, isActive: isConnecting)
Iterates through the symbol's layers, highlighting each in sequence. Creates a "filling up" or "cycling" look.
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative, isActive: isSearching)
Variants:
.variableColor.iterative — highlights one layer at a time.variableColor.cumulative — progressively fills layers.variableColor.reversing — cycles back and forth.variableColor.iterative.reversingScales the symbol up or down.
Image(systemName: "mic.fill")
.symbolEffect(.scale.up, isActive: isRecording)
A smooth, rhythmic scale animation — like the symbol is breathing.
Image(systemName: "heart.fill")
.symbolEffect(.breathe, isActive: isMonitoring)
Variants: .breathe.plain (scale only), .breathe.pulse (scale + opacity)
Continuous rotation for processing indicators.
Image(systemName: "gear")
.symbolEffect(.rotate, isActive: isProcessing)
All effects accept SymbolEffectOptions via the options parameter.
// Repeat 3 times
.symbolEffect(.bounce, options: .repeat(3), value: count)
// Double speed
.symbolEffect(.pulse, options: .speed(2.0), isActive: true)
// Repeat continuously
.symbolEffect(.variableColor, options: .repeat(.continuous), isActive: true)
// Non-repeating (run once)
.symbolEffect(.breathe, options: .nonRepeating, isActive: true)
// Combine options
.symbolEffect(.bounce, options: .repeat(5).speed(1.5), value: count)
Used when a symbol-based view appears or disappears from the view hierarchy.
if showSymbol {
Image(systemName: "checkmark.circle.fill")
.transition(.symbolEffect(.appear))
}
Available transitions: .appear, .disappear
Variants: .appear.up, .appear.down, .disappear.up, .disappear.down
Used to animate from one symbol to another. Applied to the container, not the symbol.
@State private var isFavorite = false
Button {
isFavorite.toggle()
} label: {
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
}
Replace variants:
.replace.downUp — old symbol moves down, new moves up.replace.upUp — both move up.replace.offUp — old fades off, new moves upWhen two symbols share a common structure (like star and star.fill, or pause.fill and play.fill), Replace automatically performs a Magic Replace — morphing shared elements while transitioning differing parts. Magic Replace is the default behavior for .replace in iOS 18+. For explicit control:
// Explicit Magic Replace with fallback
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
Draw animations simulate the natural flow of drawing a symbol with a pen. This is the signature new feature in SF Symbols 7.
Draw On animates a symbol appearing by "drawing" it stroke by stroke. Draw Off animates a symbol disappearing by "erasing" it.
// Draw On — symbol draws in when isComplete becomes true
Image(systemName: "checkmark.circle")
.symbolEffect(.drawOn, isActive: isComplete)
// Draw Off — symbol draws out when isHidden becomes true
Image(systemName: "star.fill")
.symbolEffect(.drawOff, isActive: isHidden)
Control how multi-layer symbols animate their draw:
// By Layer (default) — staggered timing, layers overlap
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.byLayer, isActive: showIcon)
// Whole Symbol — all layers draw simultaneously
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.wholeSymbol, isActive: showIcon)
// Individually — sequential, each layer completes before next starts
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.individually, isActive: showIcon)
When to use each mode:
Draw Off supports controlling whether the animation plays forward or in reverse:
// Forward (default) — follows the draw path
.symbolEffect(.drawOff.nonReversed, isActive: isHidden)
// Reversed — erases in reverse order of how it was drawn
.symbolEffect(.drawOff.reversed, isActive: isErasing)
Variable Draw uses SymbolVariableValueMode.draw to partially draw a symbol's stroke path based on a 0.0 to 1.0 value — perfect for progress indicators.
Image(systemName: "thermometer.high", variableValue: temperature)
.symbolVariableValueMode(.draw) // iOS 26+
Compare with traditional Variable Color (which sets opacity per layer):
Image(systemName: "wifi", variableValue: signalStrength)
.symbolVariableValueMode(.color) // iOS 17+ (default behavior)
Constraint: A symbol can support both Variable Color and Variable Draw, but only one mode can be active at render time. Setting an unsupported mode has no visible effect.
SF Symbols 7 introduces SymbolColorRenderingMode for gradient fills generated from a single source color.
Image(systemName: "star.fill")
.symbolColorRenderingMode(.gradient) // iOS 26+
.foregroundStyle(.red)
| Mode | Description |
|---|---|
.flat | Solid color fill (default) |
.gradient | Axial gradient from source color |
Gradients work with all rendering modes and are most effective at larger sizes.
When using .contentTransition(.symbolEffect(.replace)) between certain symbol pairs, the system now combines Draw Off on the outgoing symbol with Draw On for the incoming symbol. The enclosure (if shared, like a circle outline) is preserved while inner elements transition with draw animations.
// Automatic Draw-enhanced Magic Replace
Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
.contentTransition(.symbolEffect(.replace))
To enable Draw animations on custom symbols, annotate paths in the SF Symbols app:
Option-drag guide points for precise placement. Use context menus to configure direction and end caps.
| Pattern | Problem | Fix |
|---|---|---|
| Palette with 1 color | Equivalent to Monochrome, wasted API call | Use Monochrome or provide multiple colors |
| Multicolor for branded icons | Can't customize Apple's fixed colors | Use Palette with brand colors |
Hardcoded .foregroundColor(.blue) | Ignores Dark Mode, Dynamic Type, accessibility | Use .foregroundStyle() with semantic colors |
| Hierarchical for status indicators | Layers don't carry distinct meaning | Use Palette with semantic colors |
| Pattern | Problem | Fix |
|---|---|---|
| Bounce for loading state | One-shot, doesn't convey "ongoing" | Use Pulse, Breathe, or Variable Color |
| Pulse for tap feedback | Too subtle for confirming action | Use Bounce |
| Continuous Rotate for non-mechanical symbols | Looks unnatural for organic shapes | Use Breathe for organic symbols |
| Draw On for transient state changes | Too dramatic for frequent toggles | Use Replace or Scale |
// ❌ Crashes on iOS 17
Image(systemName: "bell")
.symbolEffect(.wiggle, value: count) // Wiggle requires iOS 18+
// ✅ Safe version check
Image(systemName: "bell")
.modifier(BellEffectModifier(count: count))
struct BellEffectModifier: 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)
}
}
}
Symbol effects automatically respect the Reduce Motion accessibility setting — most effects are suppressed or simplified. However, if you're using effects to convey essential information (not just decoration), provide an alternative:
// Variable Color conveys WiFi strength — provide text fallback
Image(systemName: "wifi")
.symbolEffect(.variableColor, isActive: isSearching)
.accessibilityLabel("Searching for WiFi networks")
Do not disable Reduce Motion or try to force-play effects. The system handles this correctly.
// ❌ VoiceOver says "star.fill"
Image(systemName: "star.fill")
// ✅ VoiceOver says "Favorite"
Image(systemName: "star.fill")
.accessibilityLabel("Favorite")
When using .contentTransition(.symbolEffect(.replace)) to swap symbols, update the accessibility label to match the current state:
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
Symptom: .symbolEffect() modifier applied but no animation visible.
value: that changes. Indefinite effects need isActive: true. Transition effects need the view to actually enter/leave the hierarchy.symbolEffect() modifiers on the same view can conflict. Use a single effect or combine with optionsSymptom: Symbol colors don't match expected appearance.
.foregroundStyle but see only one color, you may need .symbolRenderingMode(.palette) or .hierarchical.tint vs .foregroundStyle — In UIKit, tintColor affects Monochrome and Hierarchical. For Palette, use UIImage.SymbolConfiguration(paletteColors:).foregroundStyle from a parent view may override your rendering mode. Apply .symbolRenderingMode() directly on the ImageSymptom: Custom symbol looks too thin or too thick next to text or other symbols.
.font() alignment — The symbol's weight follows the applied font weight. If using .font(.title), ensure your custom symbol has appropriate weight variants.imageScale(.small/.medium/.large) affects overall size. Use .font() for weight matchingSymptom: .symbolEffect(.drawOn) applied to custom symbol but no draw animation occurs.
Setup: Designer provides PNG icons. Developer considers using them instead of SF Symbols.
Why this matters: Static PNGs don't adapt to Dynamic Type, Bold Text, Dark Mode, or accessibility settings. They also don't support symbol effects.
Professional response: "SF Symbols scale with text, support 9 weights, adapt to Dark Mode and Bold Text automatically, and enable animations without custom code. A PNG requires @1x/@2x/@3x variants, manual Dark Mode handling, manual Dynamic Type scaling, and custom animation code. The 10 minutes to find the right SF Symbol saves hours of asset management."
Time cost of skipping: 2-4 hours managing assets + ongoing maintenance vs 10 minutes finding the right symbol.
Setup: Sprint deadline. PM says animations are polish and can wait.
Why this matters: Retrofitting symbol effects requires restructuring state management. Effects triggered by value: changes need the right state architecture from the start.
Professional response: "Adding .symbolEffect(.bounce, value: count) takes one line. Retrofitting the state to support it later takes a refactor. Let me add the effect now — it's literally one modifier."
Setup: Custom symbols have Draw animations that look wrong — paths draw in unexpected order or direction.
Why this matters: Draw annotation requires intentional guide point placement. Without it, the system guesses and often gets it wrong.
Fix: Open custom symbols in SF Symbols 7 app, add guide points explicitly to each path defining start/end/direction. Test each weight variant. See Custom Symbol Draw Annotation section above.
WWDC: 2023-10257, 2023-10258, 2024-10188, 2025-337
Docs: /symbols, /symbols/symboleffect, /symbols/symbolrenderingmode, /swiftui/image/symboleffect(:options:value:), /swiftui/image/symbolrenderingmode(:)
Skills: axiom-sf-symbols-ref, axiom-hig-ref, axiom-swiftui-animation-ref
Last Updated Based on WWDC 2023/10257-10258, WWDC 2024/10188, WWDC 2025/337 Version iOS 17+ (effects), iOS 18+ (Wiggle/Rotate/Breathe), iOS 26+ (Draw On/Off, Variable Draw, Gradients)
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.