home / skills / charleswiltgen / axiom / axiom-sf-symbols-ref

This skill provides complete SF Symbols API reference with rendering modes, effects, configurations, UIKit equivalents, and platform availability for iOS 17-26.

npx playbooks add skill charleswiltgen/axiom --skill axiom-sf-symbols-ref

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
27.1 KB
---
name: axiom-sf-symbols-ref
description: Use when you need complete SF Symbols API reference including every rendering mode, symbol effect, configuration option, UIKit equivalent, and platform availability - comprehensive code examples for iOS 17 through iOS 26
license: MIT
compatibility: iOS 17+, iOS 18+, iOS 26+
metadata:
  version: "1.0.0"
---

# SF Symbols — API Reference

## When to Use This Skill

Use when:
- You need exact API signatures for rendering modes or symbol effects
- You need UIKit/AppKit equivalents for SwiftUI symbol APIs
- You need to check platform availability for a specific effect
- You need configuration options (weight, scale, variable values)
- You need to create custom symbols with proper template structure

#### Related Skills
- Use `axiom-sf-symbols` for decision trees, anti-patterns, troubleshooting, and when to use which effect
- Use `axiom-swiftui-animation-ref` for general SwiftUI animation (non-symbol)

---

## Part 1: Symbol Display

### SwiftUI

```swift
// Basic display
Image(systemName: "star.fill")

// With Label (icon + text)
Label("Favorites", systemImage: "star.fill")

// Font sizing — symbol scales with text
Image(systemName: "star.fill")
    .font(.title)

// Image scale — relative sizing without changing font
Image(systemName: "star.fill")
    .imageScale(.large) // .small, .medium, .large

// Explicit point size
Image(systemName: "star.fill")
    .font(.system(size: 24))

// Weight — matches SF Pro font weights
Image(systemName: "star.fill")
    .fontWeight(.bold) // .ultraLight through .black

// Symbol variant — programmatic .fill, .circle, .square, .slash
Image(systemName: "person")
    .symbolVariant(.circle.fill) // Renders person.circle.fill

// Variable value — 0.0 to 1.0, controls symbol fill level
Image(systemName: "speaker.wave.3.fill", variableValue: 0.5)
```

### UIKit

```swift
// Basic display
let image = UIImage(systemName: "star.fill")
imageView.image = image

// Configuration — point size and weight
let config = UIImage.SymbolConfiguration(pointSize: 24, weight: .bold)
let image = UIImage(systemName: "star.fill", withConfiguration: config)

// Configuration — text style (scales with Dynamic Type)
let config = UIImage.SymbolConfiguration(textStyle: .title1)
let image = UIImage(systemName: "star.fill", withConfiguration: config)

// Configuration — scale
let config = UIImage.SymbolConfiguration(scale: .large) // .small, .medium, .large

// Combine configurations
let sizeConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .bold, scale: .large)

// Variable value
let image = UIImage(systemName: "speaker.wave.3.fill", variableValue: 0.5)
```

### AppKit

```swift
// Basic display
let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: "Favorite")

// Configuration
let config = NSImage.SymbolConfiguration(pointSize: 24, weight: .bold)
let configured = image?.withSymbolConfiguration(config)
```

---

## Part 2: Rendering Modes

### SwiftUI

```swift
// Monochrome (default)
Image(systemName: "cloud.rain.fill")
    .foregroundStyle(.blue)

// Hierarchical — depth from single color
Image(systemName: "cloud.rain.fill")
    .symbolRenderingMode(.hierarchical)
    .foregroundStyle(.blue)

// Palette — explicit color per layer
Image(systemName: "cloud.rain.fill")
    .symbolRenderingMode(.palette)
    .foregroundStyle(.white, .blue)
// For 3-layer symbols:
    .foregroundStyle(.red, .white, .blue)

// Multicolor — Apple's curated colors
Image(systemName: "cloud.rain.fill")
    .symbolRenderingMode(.multicolor)

// Preferred rendering mode — uses symbol's preferred mode
// Falls back gracefully if the symbol doesn't support it
Image(systemName: "cloud.rain.fill")
    .symbolRenderingMode(.monochrome) // explicit monochrome
```

#### SymbolRenderingMode Enum

| Value | Description |
|-------|-------------|
| `.monochrome` | Single color for all layers (default) |
| `.hierarchical` | Single color with automatic opacity per layer |
| `.palette` | Explicit color per layer via `.foregroundStyle()` |
| `.multicolor` | Apple's fixed curated colors |

### UIKit

```swift
// Hierarchical
let config = UIImage.SymbolConfiguration(hierarchicalColor: .systemBlue)
imageView.preferredSymbolConfiguration = config

// Palette
let config = UIImage.SymbolConfiguration(paletteColors: [.white, .systemBlue])
imageView.preferredSymbolConfiguration = config

// Multicolor
let config = UIImage.SymbolConfiguration.preferringMulticolor()
imageView.preferredSymbolConfiguration = config

// Monochrome — just set tintColor
imageView.tintColor = .systemBlue
```

### Combining Configurations (UIKit)

```swift
let sizeConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .bold)
let colorConfig = UIImage.SymbolConfiguration(paletteColors: [.white, .blue, .gray])
let combined = sizeConfig.applying(colorConfig)
imageView.preferredSymbolConfiguration = combined
```

---

## Part 3: Symbol Effects — Complete API

### Effect Protocol Hierarchy

All symbol effects conform to `SymbolEffect`. Sub-protocols define behavior:

| Protocol | Trigger | Modifier | Loop |
|----------|---------|----------|------|
| `DiscreteSymbolEffect` | `value:` (Equatable) | `.symbolEffect(_:options:value:)` | No |
| `IndefiniteSymbolEffect` | `isActive:` (Bool) | `.symbolEffect(_:options:isActive:)` | Yes |
| `TransitionSymbolEffect` | View lifecycle | `.transition(.symbolEffect(_:))` | No |
| `ContentTransitionSymbolEffect` | Symbol change | `.contentTransition(.symbolEffect(_:))` | No |

### Remove All Effects (SwiftUI)

```swift
// Strip all symbol effects from a view hierarchy
Image(systemName: "star.fill")
    .symbolEffectsRemoved() // Removes all effects
    .symbolEffectsRemoved(false) // Re-enables effects
```

### SymbolEffectOptions

```swift
// Speed multiplier
.symbolEffect(.bounce, options: .speed(2.0), value: count)

// Repeat count
.symbolEffect(.bounce, options: .repeat(3), value: count)

// Continuous repeat
.symbolEffect(.pulse, options: .repeat(.continuous), isActive: true)

// Non-repeating (for indefinite effects, run once then hold)
.symbolEffect(.breathe, options: .nonRepeating, isActive: true)

// Combined
.symbolEffect(.wiggle, options: .repeat(5).speed(1.5), value: count)
```

---

### Bounce

**Protocols**: `DiscreteSymbolEffect`

```swift
// Discrete — triggers on value change
Image(systemName: "arrow.down.circle")
    .symbolEffect(.bounce, value: downloadCount)

// Directional
    .symbolEffect(.bounce.up, value: count)
    .symbolEffect(.bounce.down, value: count)

// By Layer — different layers bounce at different times
    .symbolEffect(.bounce.byLayer, value: count)

// Whole Symbol — entire symbol bounces together
    .symbolEffect(.bounce.wholeSymbol, value: count)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.bounce)
// With options:
imageView.addSymbolEffect(.bounce, options: .repeat(3))
```

---

### Pulse

**Protocols**: `DiscreteSymbolEffect`, `IndefiniteSymbolEffect`

```swift
// Indefinite — continuous while active
Image(systemName: "network")
    .symbolEffect(.pulse, isActive: isConnecting)

// Discrete — triggers once on value change
    .symbolEffect(.pulse, value: errorCount)

// By Layer
    .symbolEffect(.pulse.byLayer, isActive: true)

// Whole Symbol
    .symbolEffect(.pulse.wholeSymbol, isActive: true)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.pulse)
imageView.removeSymbolEffect(ofType: PulseSymbolEffect.self)
```

---

### Variable Color

**Protocols**: `DiscreteSymbolEffect`, `IndefiniteSymbolEffect`

```swift
// Iterative — highlights one layer at a time
Image(systemName: "wifi")
    .symbolEffect(.variableColor.iterative, isActive: isSearching)

// Cumulative — progressively fills layers
    .symbolEffect(.variableColor.cumulative, isActive: true)

// Reversing — cycles back and forth
    .symbolEffect(.variableColor.iterative.reversing, isActive: true)

// Hide inactive layers (dims non-highlighted layers)
    .symbolEffect(.variableColor.iterative.hideInactiveLayers, isActive: true)

// Dim inactive layers (slightly reduces opacity of non-highlighted)
    .symbolEffect(.variableColor.iterative.dimInactiveLayers, isActive: true)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.variableColor.iterative)
imageView.removeSymbolEffect(ofType: VariableColorSymbolEffect.self)
```

---

### Scale

**Protocols**: `IndefiniteSymbolEffect`

```swift
// Scale up
Image(systemName: "mic.fill")
    .symbolEffect(.scale.up, isActive: isRecording)

// Scale down
    .symbolEffect(.scale.down, isActive: isMuted)

// By Layer
    .symbolEffect(.scale.up.byLayer, isActive: true)

// Whole Symbol
    .symbolEffect(.scale.up.wholeSymbol, isActive: true)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.scale.up)
imageView.removeSymbolEffect(ofType: ScaleSymbolEffect.self)
```

---

### Wiggle (iOS 18+)

**Protocols**: `DiscreteSymbolEffect`, `IndefiniteSymbolEffect`

```swift
// Discrete
Image(systemName: "bell.fill")
    .symbolEffect(.wiggle, value: notificationCount)

// Directional
    .symbolEffect(.wiggle.left, value: count)
    .symbolEffect(.wiggle.right, value: count)
    .symbolEffect(.wiggle.forward, value: count)  // RTL-aware
    .symbolEffect(.wiggle.backward, value: count)  // RTL-aware
    .symbolEffect(.wiggle.up, value: count)
    .symbolEffect(.wiggle.down, value: count)
    .symbolEffect(.wiggle.clockwise, value: count)
    .symbolEffect(.wiggle.counterClockwise, value: count)

// Custom angle
    .symbolEffect(.wiggle.custom(angle: .degrees(15)), value: count)

// By Layer
    .symbolEffect(.wiggle.byLayer, value: count)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.wiggle)
```

---

### Rotate (iOS 18+)

**Protocols**: `DiscreteSymbolEffect`, `IndefiniteSymbolEffect`

```swift
// Indefinite rotation
Image(systemName: "gear")
    .symbolEffect(.rotate, isActive: isProcessing)

// Direction
    .symbolEffect(.rotate.clockwise, isActive: true)
    .symbolEffect(.rotate.counterClockwise, isActive: true)

// By Layer — only specific layers rotate (e.g., fan blades)
    .symbolEffect(.rotate.byLayer, isActive: true)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.rotate)
imageView.removeSymbolEffect(ofType: RotateSymbolEffect.self)
```

---

### Breathe (iOS 18+)

**Protocols**: `DiscreteSymbolEffect`, `IndefiniteSymbolEffect`

```swift
// Basic breathe
Image(systemName: "heart.fill")
    .symbolEffect(.breathe, isActive: isMonitoring)

// Plain — scale only
    .symbolEffect(.breathe.plain, isActive: true)

// Pulse — scale + opacity variation
    .symbolEffect(.breathe.pulse, isActive: true)

// By Layer
    .symbolEffect(.breathe.byLayer, isActive: true)
```

**UIKit**:
```swift
imageView.addSymbolEffect(.breathe)
imageView.removeSymbolEffect(ofType: BreatheSymbolEffect.self)
```

---

### Appear and Disappear

**Protocols**: `TransitionSymbolEffect`

```swift
// SwiftUI transition
if showSymbol {
    Image(systemName: "checkmark.circle.fill")
        .transition(.symbolEffect(.appear))
}

if showSymbol {
    Image(systemName: "xmark.circle.fill")
        .transition(.symbolEffect(.disappear))
}

// Directional
    .transition(.symbolEffect(.appear.up))
    .transition(.symbolEffect(.appear.down))
    .transition(.symbolEffect(.disappear.up))
    .transition(.symbolEffect(.disappear.down))

// By Layer
    .transition(.symbolEffect(.appear.byLayer))

// Whole Symbol
    .transition(.symbolEffect(.appear.wholeSymbol))
```

**UIKit** (as effect, not transition):
```swift
// Make symbol appear
imageView.addSymbolEffect(.appear)

// Make symbol disappear
imageView.addSymbolEffect(.disappear)

// Appear after disappear
imageView.addSymbolEffect(.appear) // re-shows hidden symbol
```

---

### Replace

**Protocols**: `ContentTransitionSymbolEffect`

```swift
// SwiftUI content transition
Image(systemName: isFavorite ? "star.fill" : "star")
    .contentTransition(.symbolEffect(.replace))

// Directional variants
    .contentTransition(.symbolEffect(.replace.downUp))
    .contentTransition(.symbolEffect(.replace.upUp))
    .contentTransition(.symbolEffect(.replace.offUp))

// By Layer
    .contentTransition(.symbolEffect(.replace.byLayer))

// Whole Symbol
    .contentTransition(.symbolEffect(.replace.wholeSymbol))

// Magic Replace — default in iOS 18+, morphs shared elements
// Automatic for structurally related pairs: star ↔ star.fill, pause.fill ↔ play.fill
    .contentTransition(.symbolEffect(.replace))

// Explicit Magic Replace with fallback for unrelated symbols
    .contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
```

**UIKit**:
```swift
// Change symbol with Replace transition
let newImage = UIImage(systemName: "star.fill")
imageView.setSymbolImage(newImage!, contentTransition: .replace)

// Directional
imageView.setSymbolImage(newImage!, contentTransition: .replace.downUp)
```

---

## Part 4: Draw Effects (iOS 26+)

### Draw On

```swift
// Indefinite — draws in while active
Image(systemName: "checkmark.circle")
    .symbolEffect(.drawOn, isActive: isComplete)

// Playback modes
    .symbolEffect(.drawOn.byLayer, isActive: isActive)
    .symbolEffect(.drawOn.wholeSymbol, isActive: isActive)
    .symbolEffect(.drawOn.individually, isActive: isActive)

// With options
    .symbolEffect(.drawOn, options: .speed(2.0), isActive: isActive)
    .symbolEffect(.drawOn, options: .nonRepeating, isActive: isActive)
```

### Draw Off

```swift
// Indefinite — draws out while active
Image(systemName: "star.fill")
    .symbolEffect(.drawOff, isActive: isHidden)

// Playback modes
    .symbolEffect(.drawOff.byLayer, isActive: isActive)
    .symbolEffect(.drawOff.wholeSymbol, isActive: isActive)
    .symbolEffect(.drawOff.individually, isActive: isActive)

// Direction control
    .symbolEffect(.drawOff.nonReversed, isActive: isActive) // follows draw path forward
    .symbolEffect(.drawOff.reversed, isActive: isActive)    // erases in reverse order
```

### UIKit Draw Effects

```swift
// Draw On
imageView.addSymbolEffect(.drawOn)

// Draw Off
imageView.addSymbolEffect(.drawOff)

// Remove
imageView.removeSymbolEffect(ofType: DrawOnSymbolEffect.self)
```

### Variable Draw

Uses `SymbolVariableValueMode` to control how variable values are rendered.

```swift
// Variable Draw — draws stroke proportional to value (iOS 26+)
Image(systemName: "thermometer.high", variableValue: temperature)
    .symbolVariableValueMode(.draw)

// Variable Color — sets layer opacity based on threshold (iOS 17+, default)
Image(systemName: "wifi", variableValue: signalStrength)
    .symbolVariableValueMode(.color)
```

#### SymbolVariableValueMode Enum (iOS 26+)

| Case | Description |
|------|-------------|
| `.color` | Sets opacity of each variable layer on/off based on threshold (existing behavior) |
| `.draw` | Changes drawn length of each variable layer based on range |

**Constraint**: Some symbols support only one mode. Setting an unsupported mode has no visible effect. A symbol cannot use both Variable Color and Variable Draw simultaneously.

### Gradient Rendering (iOS 26+)

Uses `SymbolColorRenderingMode` for automatic gradient generation from a single color.

```swift
// Gradient fill — system generates axial gradient from source color
Image(systemName: "heart.fill")
    .symbolColorRenderingMode(.gradient)
    .foregroundStyle(.red)

// Works with any rendering mode
Image(systemName: "cloud.rain.fill")
    .symbolRenderingMode(.hierarchical)
    .symbolColorRenderingMode(.gradient)
    .foregroundStyle(.blue)
```

#### SymbolColorRenderingMode Enum (iOS 26+)

| Case | Description |
|------|-------------|
| `.flat` | Solid color fill (default) |
| `.gradient` | Axial gradient generated from source color |

Gradients are most effective at larger symbol sizes and work across all rendering modes.

---

## Part 5: Content Transition Patterns

### Symbol Swap with Replace

```swift
struct PlayPauseButton: View {
    @State private var isPlaying = false

    var body: some View {
        Button {
            isPlaying.toggle()
        } label: {
            Image(systemName: isPlaying ? "pause.fill" : "play.fill")
                .contentTransition(.symbolEffect(.replace))
        }
        .accessibilityLabel(isPlaying ? "Pause" : "Play")
    }
}
```

### Download Progress Pattern

```swift
struct DownloadButton: View {
    @State private var state: DownloadState = .idle

    var symbolName: String {
        switch state {
        case .idle: "arrow.down.circle"
        case .downloading: "stop.circle"
        case .complete: "checkmark.circle.fill"
        }
    }

    var body: some View {
        Button {
            advanceState()
        } label: {
            Image(systemName: symbolName)
                .contentTransition(.symbolEffect(.replace))
                .symbolEffect(.pulse, isActive: state == .downloading)
        }
    }
}
```

### Toggle with Effect Feedback

```swift
struct FavoriteButton: View {
    @Binding var isFavorite: Bool
    @State private var bounceValue = 0

    var body: some View {
        Button {
            isFavorite.toggle()
            bounceValue += 1
        } label: {
            Image(systemName: isFavorite ? "star.fill" : "star")
                .contentTransition(.symbolEffect(.replace))
                .symbolEffect(.bounce, value: bounceValue)
                .foregroundStyle(isFavorite ? .yellow : .gray)
        }
    }
}
```

---

## Part 6: Custom Symbols

### Template Structure

Custom symbols are SVG files with specific layer annotations:

1. **Export from design tool** as SVG
2. **Import into SF Symbols app** (File > Import)
3. **Set template type**: Monochrome, Hierarchical, Multicolor, or Variable Color
4. **Annotate layers** for rendering modes:
   - **Primary** layer: Full opacity in Hierarchical
   - **Secondary** layer: Reduced opacity in Hierarchical
   - **Tertiary** layer: Most reduced opacity in Hierarchical
5. **Set Palette colors** per layer if supporting Palette mode
6. **Export** as `.svg` template for Xcode

### Draw Annotation (SF Symbols 7)

To enable Draw animations on custom symbols:

1. Select a path in SF Symbols 7 app
2. Open the Draw annotation panel
3. Place guide points on the path:

| Point Type | Visual | Purpose |
|------------|--------|---------|
| Start | Open circle | Where drawing begins |
| End | Closed circle | Where drawing ends |
| Corner | Diamond | Sharp direction change |
| Bidirectional | Double arrow | Center-outward drawing |
| Attachment | Link icon | Non-drawing decorative connection |

4. **Minimum**: 2 guide points per path (start + end)
5. **Option-drag** for precise placement
6. Test in Preview panel across all weights

### Weight Interpolation

Custom symbols should include designs for at least 3 weight variants:
- **Ultralight** (thinnest)
- **Regular** (middle)
- **Black** (thickest)

The system interpolates between these for intermediate weights (Thin, Light, Medium, Semibold, Bold, Heavy).

### Importing to Xcode

1. In Xcode, open Asset Catalog
2. Click **+** > **Symbol Image Set**
3. Drag exported `.svg` from SF Symbols app
4. Asset catalog symbols: `Image("custom.symbol.name")`. For symbols loaded from a bundle: `Image(systemName: "custom.symbol.name", bundle: .module)`

---

## Part 7: Platform Availability Matrix

### Rendering Modes

| Feature | iOS | macOS | watchOS | tvOS | visionOS |
|---------|-----|-------|---------|------|----------|
| Monochrome | 13+ | 11+ | 6+ | 13+ | 1+ |
| Hierarchical | 15+ | 12+ | 8+ | 15+ | 1+ |
| Palette | 15+ | 12+ | 8+ | 15+ | 1+ |
| Multicolor | 15+ | 12+ | 8+ | 15+ | 1+ |
| Variable Value | 16+ | 13+ | 9+ | 16+ | 1+ |

### Symbol Effects

| Effect | Category | iOS | macOS | watchOS | tvOS | visionOS |
|--------|----------|-----|-------|---------|------|----------|
| Bounce | Discrete | 17+ | 14+ | 10+ | 17+ | 1+ |
| Pulse | Discrete/Indefinite | 17+ | 14+ | 10+ | 17+ | 1+ |
| Variable Color | Discrete/Indefinite | 17+ | 14+ | 10+ | 17+ | 1+ |
| Scale | Indefinite | 17+ | 14+ | 10+ | 17+ | 1+ |
| Appear | Transition | 17+ | 14+ | 10+ | 17+ | 1+ |
| Disappear | Transition | 17+ | 14+ | 10+ | 17+ | 1+ |
| Replace | Content Transition | 17+ | 14+ | 10+ | 17+ | 1+ |
| Wiggle | Discrete/Indefinite | 18+ | 15+ | 11+ | 18+ | 2+ |
| Rotate | Discrete/Indefinite | 18+ | 15+ | 11+ | 18+ | 2+ |
| Breathe | Discrete/Indefinite | 18+ | 15+ | 11+ | 18+ | 2+ |
| Draw On | Indefinite | 26+ | Tahoe+ | 26+ | 26+ | 26+ |
| Draw Off | Indefinite | 26+ | Tahoe+ | 26+ | 26+ | 26+ |
| Variable Draw | Value-based | 26+ | Tahoe+ | 26+ | 26+ | 26+ |
| Gradient Fill | Rendering | 26+ | Tahoe+ | 26+ | 26+ | 26+ |

### Effect Behavior Categories

| Category | What It Does | How to Trigger |
|----------|-------------|----------------|
| Discrete | One-shot animation, returns to rest | `.symbolEffect(_:value:)` — fires when value changes |
| Indefinite | Loops while active | `.symbolEffect(_:isActive:)` — loops while `true` |
| Transition | Plays on view insert/remove | `.transition(.symbolEffect(_:))` |
| Content Transition | Plays when symbol changes | `.contentTransition(.symbolEffect(_:))` |

---

## Part 8: UIKit Complete Reference

### Adding Effects

```swift
// Add indefinite effect
imageView.addSymbolEffect(.pulse)
imageView.addSymbolEffect(.breathe)
imageView.addSymbolEffect(.rotate)
imageView.addSymbolEffect(.variableColor.iterative)
imageView.addSymbolEffect(.scale.up)

// Add with options
imageView.addSymbolEffect(.bounce, options: .repeat(3))
imageView.addSymbolEffect(.pulse, options: .speed(2.0))

// Add with completion handler
imageView.addSymbolEffect(.bounce, options: .default) { context in
    // Called when effect finishes
    print("Bounce complete")
}
```

### Removing Effects

```swift
// Remove specific effect type
imageView.removeSymbolEffect(ofType: PulseSymbolEffect.self)
imageView.removeSymbolEffect(ofType: ScaleSymbolEffect.self)
imageView.removeSymbolEffect(ofType: RotateSymbolEffect.self)

// Remove all effects
imageView.removeAllSymbolEffects()

// Remove with options
imageView.removeSymbolEffect(ofType: PulseSymbolEffect.self, options: .default)

// Remove with completion
imageView.removeSymbolEffect(ofType: PulseSymbolEffect.self) { context in
    print("Pulse removed")
}
```

### Setting Symbol Images with Transitions

```swift
// Replace with content transition
let newImage = UIImage(systemName: "pause.fill")!
imageView.setSymbolImage(newImage, contentTransition: .replace)

// Directional replace
imageView.setSymbolImage(newImage, contentTransition: .replace.downUp)
imageView.setSymbolImage(newImage, contentTransition: .replace.upUp)
imageView.setSymbolImage(newImage, contentTransition: .replace.offUp)

// With options
imageView.setSymbolImage(newImage, contentTransition: .replace, options: .speed(2.0))
```

### UIBarButtonItem Effects

```swift
// Effects also work on UIBarButtonItem
barButtonItem.addSymbolEffect(.bounce)
barButtonItem.addSymbolEffect(.pulse, isActive: isLoading)
barButtonItem.removeSymbolEffect(ofType: PulseSymbolEffect.self)
```

---

## Part 9: Accessibility

### Labels

```swift
// SwiftUI
Image(systemName: "star.fill")
    .accessibilityLabel("Favorite")

// UIKit
let image = UIImage(systemName: "star.fill")
imageView.accessibilityLabel = "Favorite"
imageView.isAccessibilityElement = true

// Label automatically provides accessibility
Label("Settings", systemImage: "gear")
// VoiceOver reads: "Settings"
```

### Reduce Motion

Symbol effects automatically respect `UIAccessibility.isReduceMotionEnabled`. When Reduce Motion is on:
- Most effects are simplified or suppressed
- Replace transitions use crossfade instead of directional movement
- Indefinite effects may be simplified to static appearance changes

**Do not** attempt to override or check this yourself for effects. The system handles it. Only intervene if effects carry semantic meaning:

```swift
// If the pulsing conveys connection status, provide a text label
Image(systemName: "wifi")
    .symbolEffect(.pulse, isActive: isConnecting)
    .accessibilityLabel(isConnecting ? "Connecting to WiFi" : "WiFi connected")
```

### Bold Text

SF Symbols automatically adapt when Bold Text is enabled in Accessibility settings. Custom symbols need weight variants to support this properly.

### Dynamic Type

Symbols sized with `.font()` scale automatically with Dynamic Type. Symbols sized with explicit point sizes (`.font(.system(size: 24))`) do **not** scale.

```swift
// ✅ Scales with Dynamic Type
Image(systemName: "star.fill")
    .font(.title)

// ❌ Fixed size, does not scale
Image(systemName: "star.fill")
    .font(.system(size: 24))
```

---

## Part 10: Common Patterns

### Notification Badge with Effect

```swift
struct NotificationBell: View {
    let count: Int

    var body: some View {
        Image(systemName: count > 0 ? "bell.badge.fill" : "bell.fill")
            .contentTransition(.symbolEffect(.replace))
            .symbolEffect(.wiggle, value: count)
            .symbolRenderingMode(.palette)
            .foregroundStyle(count > 0 ? .red : .primary, .primary)
    }
}
```

### WiFi Strength Indicator

```swift
struct WiFiIndicator: View {
    let strength: Double // 0.0 to 1.0
    let isSearching: Bool

    var body: some View {
        Image(systemName: "wifi", variableValue: strength)
            .symbolEffect(.variableColor.iterative, isActive: isSearching)
            .symbolRenderingMode(.hierarchical)
            .accessibilityLabel(
                isSearching ? "Searching for WiFi" :
                "WiFi strength: \(Int(strength * 100))%"
            )
    }
}
```

### Animated Toggle

```swift
struct RecordButton: View {
    @State private var isRecording = false

    var body: some View {
        Button {
            isRecording.toggle()
        } label: {
            Image(systemName: isRecording ? "stop.circle.fill" : "record.circle")
                .contentTransition(.symbolEffect(.replace))
                .symbolEffect(.breathe.pulse, isActive: isRecording)
                .font(.largeTitle)
                .foregroundStyle(isRecording ? .red : .primary)
        }
        .accessibilityLabel(isRecording ? "Stop recording" : "Start recording")
    }
}
```

### Multi-State Symbol with Draw (iOS 26+)

```swift
struct TaskCheckbox: View {
    @State private var isComplete = false

    var body: some View {
        Button {
            isComplete.toggle()
        } label: {
            Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
                .contentTransition(.symbolEffect(.replace))
                .symbolEffect(.drawOn, isActive: isComplete)
                .font(.title2)
                .foregroundStyle(isComplete ? .green : .secondary)
        }
        .accessibilityLabel(isComplete ? "Completed" : "Not completed")
    }
}
```

---

## Resources

**WWDC**: 2023-10257, 2023-10258, 2024-10188, 2025-337

**Docs**: /symbols, /symbols/symboleffect, /symbols/bouncesymboleffect, /symbols/pulsesymboleffect, /symbols/variablecolorsymboleffect, /symbols/scalesymboleffect, /symbols/wigglesymboleffect, /symbols/rotatesymboleffect, /symbols/breathesymboleffect, /symbols/appearsymboleffect, /symbols/disappearsymboleffect, /symbols/replacesymboleffect, /symbols/drawonsymboleffect, /symbols/drawoffsymboleffect, /swiftui/image/symbolrenderingmode(_:), /uikit/uiimage/symbolconfiguration

**Skills**: axiom-sf-symbols, axiom-hig-ref, axiom-swiftui-animation-ref

---

**Last Updated** Based on WWDC 2023/10257-10258, WWDC 2024/10188, WWDC 2025/337
**Version** iOS 13+ (display), iOS 15+ (rendering modes), iOS 17+ (effects), iOS 18+ (Wiggle/Rotate/Breathe), iOS 26+ (Draw, Gradients)

Overview

This skill provides a complete SF Symbols API reference covering rendering modes, symbol effects, configuration options, UIKit/AppKit equivalents, and platform availability. It targets modern xOS development with comprehensive code examples for iOS 17 through iOS 26+. Use it when you need exact signatures, behavior details, or migration guidance between SwiftUI and UIKit/AppKit.

How this skill works

The reference enumerates SwiftUI, UIKit, and AppKit usage for symbol display, size/weight configuration, rendering modes (monochrome, hierarchical, palette, multicolor), and how to combine UIImage/NSImage SymbolConfigurations. It catalogs every symbol effect type (discrete, indefinite, transition, content transition) with available options like speed, repetition, and variable values, plus platform availability and example calls for adding/removing effects in UIKit and SwiftUI.

When to use it

  • You need exact API signatures for rendering modes, symbol effects, or configuration constructors.
  • You want UIKit or AppKit equivalents for SwiftUI symbol APIs when bridging view layers.
  • You must verify platform availability (iOS 17–26+) for a specific symbol effect or draw feature.
  • You are building custom symbols or need correct template structure and variableValue behavior.
  • You need concrete code examples for combining size, weight, scale, and color/palette configurations.

Best practices

  • Prefer SymbolConfiguration for UIKit to centralize pointSize/weight/scale and then apply palette/hierarchical options.
  • Use symbolRenderingMode(.palette) with .foregroundStyle(...) for deterministic multi-layer coloring; fall back to .hierarchical when fewer colors are needed.
  • Choose discrete effects for value-driven one-shot changes and indefinite effects for ongoing activity states.
  • Combine .symbolEffect options (.speed, .repeat, .nonRepeating) to control playback precisely and provide sensible defaults for accessibility.
  • When animating symbol swaps, prefer contentTransition(.symbolEffect(.replace)) for magic morphs between related symbols.

Example use cases

  • Show download progress with .bounce triggered by a changing counter and .variableValue for partial fills.
  • Display a connecting state using .pulse.isActive and a matching UIImage.SymbolConfiguration in a mixed SwiftUI/UIKit app.
  • Animate a favorites toggle using .contentTransition(.symbolEffect(.replace)) so star ↔ star.fill morphs smoothly.
  • Create a layered weather icon with .symbolRenderingMode(.palette) and three explicit colors for background, rain, and accent.
  • Implement draw-on/off effects (iOS 26+) to animate hand-drawn appearing or disappearing icons during task completion.

FAQ

Which rendering mode should I pick for consistent app branding?

Use palette for explicit multi-layer color control and hierarchical for single-color depth; multicolor is curated by Apple and not configurable.

How do I remove all effects in SwiftUI?

Call .symbolEffectsRemoved() on the view hierarchy to strip effects, or .symbolEffectsRemoved(false) to re-enable.