Developers
SF Symbols Animation in SwiftUI: 12 Effects and 5 Color Modes
Every SF Symbols animation in SwiftUI, from Bounce to Variable Draw, and the five ways to color a symbol, with each one's iOS version, on iPhone and iPhone Duo.
By AppleDuo EditorialPublished 7 min read
On this page
- How do SF Symbol animations work in SwiftUI?
- Which SF Symbols effects exist, and which iOS version added each?
- How do I play one-off and looping effects with symbolEffect?
- How do I animate a symbol in, out, or into another symbol?
- How do I show progress with Variable Color and Variable Draw?
- What are the 5 SF Symbols rendering modes in SwiftUI?
- Why do SF Symbols matter more on iPhone Duo?
- Key takeaways
SF Symbols animation in SwiftUI has grown one release at a time. iOS 17 added the first set of effects, iOS 18 added more, and iOS 26 added Draw. That makes it hard to remember which effect needs which iOS version, and which modifier plays it. iOS developer @sarunw counts 12 ways to animate a symbol and 5 ways to color one.
This guide puts all of them in one place: what each effect does, the iOS version that introduced it, the modifier that plays it, and the five color options. It's based on Apple's documentation and the SF Symbols sessions from WWDC23, WWDC24 and WWDC25. The newest APIs arrived in iOS 26, and details can change between releases.
How do SF Symbol animations work in SwiftUI?
Every symbol animation goes through the same family of modifiers, and the one you use depends on the effect's behavior. Apple's WWDC23 session Animate symbols in your app defines four behaviors:
- Discrete: plays once and stops, like Bounce.
- Indefinite: changes the symbol and keeps it changed until you remove the effect, like Scale.
- Transition: animates a symbol into or out of view, like Appear and Disappear.
- Content transition: animates from one symbol to another, like Replace.
Each behavior is a protocol in the Symbols framework, and each effect declares the behaviors it supports. SwiftUI matches them with different calls. Discrete effects use symbolEffect(_:options:value:), which plays the effect whenever the value changes. Indefinite effects use symbolEffect(_:options:isActive:), where isActive defaults to true. Both modifiers arrived in iOS 17 and work on any view that contains symbol images, not only on a single Image.
Which SF Symbols effects exist, and which iOS version added each?
Here are the 12 effects @sarunw lists, with the behaviors Apple's documentation shows for each today. Appear is listed alongside Disappear because they're a pair.
| Effect | Behavior | Introduced |
|---|---|---|
| Bounce | Discrete, indefinite | iOS 17 |
| Pulse | Discrete, indefinite | iOS 17 |
| Variable Color | Discrete, indefinite | iOS 17 |
| Scale | Indefinite | iOS 17 |
| Appear / Disappear | Transition, indefinite | iOS 17 |
| Replace | Content transition | iOS 17 |
| Wiggle | Discrete, indefinite | iOS 18 |
| Rotate | Discrete, indefinite | iOS 18 |
| Breathe | Discrete, indefinite | iOS 18 |
| Magic Replace | Content transition | iOS 18 |
| Draw On / Draw Off | Transition, indefinite | iOS 26 |
| Variable Draw | Variable value rendering | iOS 26 |
The iOS 17 set came with SF Symbols 5. Wiggle, Rotate and Breathe were introduced at WWDC24 with SF Symbols 6, and Draw and Variable Draw at WWDC25 with SF Symbols 7. Variable Draw is the odd one out: Apple calls it a rendering preset rather than an animation, but it changes how a symbol shows progress, so it belongs on the list.
How do I play one-off and looping effects with symbolEffect?
Use a discrete effect when something happens: a new message arrives, a download finishes, a button is tapped. Pass a value, and the effect plays each time it changes.
struct InboxButton: View {
@State private var newMessages = 0
var body: some View {
Button("Inbox", systemImage: "bell") { newMessages += 1 }
.symbolEffect(.bounce, value: newMessages)
}
}
Wiggle and Rotate, both new in iOS 18, use the same call. Apple's WWDC24 session suggests Wiggle for a change or call to action that might be overlooked, and Rotate for a task in progress, like spinning arrows.
Image(systemName: "bell.badge")
.symbolEffect(.wiggle, value: newMessages)
Image(systemName: "arrow.triangle.2.circlepath")
.symbolEffect(.rotate, isActive: isSyncing)
The second line shows the indefinite form. With isActive, the effect runs until the flag turns false, which suits states rather than events: syncing, recording, waiting for a connection. Pulse, Variable Color and Breathe fit here. Apple explains the difference between the two closest: Pulse changes opacity, while Breathe changes opacity and size. Scale keeps a symbol at a new size for as long as it's active.
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative, isActive: isSearching)
Image(systemName: "mic.fill")
.symbolEffect(.breathe, isActive: isRecording)
Image(systemName: "heart.fill")
.symbolEffect(.scale.up, isActive: isFavorite)
Most effects have variants. .variableColor has .iterative and .cumulative fill styles, Bounce and Scale have .up and .down, and many effects can apply .byLayer or to the .wholeSymbol. The options parameter takes SymbolEffectOptions, which sets repetition and speed.
How do I animate a symbol in, out, or into another symbol?
Appear and Disappear support two behaviors. As an indefinite effect, .symbolEffect(.disappear, isActive: isHidden) hides a symbol until the flag turns false. As a transition, the effect plays when SwiftUI inserts or removes the view.
if isConnected {
Image(systemName: "antenna.radiowaves.left.and.right")
.transition(.symbolEffect(.appear))
}
Draw On and Draw Off, new in iOS 26, follow the same pattern. Apple's documentation describes SymbolEffectTransition as applying the Appear, Disappear, DrawOn or DrawOff animation. Draw traces a symbol along its paths, in a direction each symbol defines. Hacking with Swift shows the three playback options side by side:
Image(systemName: "signature")
.symbolEffect(.drawOn, isActive: isSigned)
Image(systemName: "signature")
.symbolEffect(.drawOn.wholeSymbol, isActive: isSigned)
Image(systemName: "signature")
.symbolEffect(.drawOn.individually, isActive: isSigned)
By layer is the default and staggers each path. Whole symbol draws every layer at once, and individually draws one layer after another. Draw Off also has .reversed and .nonReversed. Custom symbols need draw annotations in the SF Symbols app before they can use Draw.
Replace swaps one symbol for another. Apply it as a content transition and change the system name:
Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.wave.2.fill")
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
Magic Replace, from iOS 18, keeps the parts two related symbols share and animates only what differs, such as a slash or a badge. Apple's WWDC24 session says it became the default Replace behavior, and that unrelated symbols fall back to a standard Replace. magic(fallback:) lets you choose that fallback. In iOS 26, Apple added enclosure matching and Draw: the outgoing slash draws off and the incoming one draws on.
How do I show progress with Variable Color and Variable Draw?
Some symbols support a variable value from 0.0 to 1.0, passed with Image(systemName:variableValue:). By default the symbol shows it with Variable Color, turning each variable layer on or off against a threshold. In iOS 26, Variable Draw instead draws each layer to a length that matches the value, over a dimmed copy of the path. Apple's examples are download progress, temperature and a timed session.
// The symbol must support Variable Draw; check it in the SF Symbols app.
Image(systemName: "thermometer.high", variableValue: temperatureLevel)
.symbolVariableValueMode(.draw)
symbolVariableValueMode(_:) takes .color or .draw. Apple notes that a symbol can support both, but only one applies when it renders.
What are the 5 SF Symbols rendering modes in SwiftUI?
Color is the other half of a symbol's look. Four modes are cases of SymbolRenderingMode, set with symbolRenderingMode(_:) since iOS 15. If you don't set one, SwiftUI picks a mode from the image and the current foreground style.
| Mode | What it does | How you set it | Introduced |
|---|---|---|---|
| Monochrome | One layer filled with the foreground style | .symbolRenderingMode(.monochrome) | iOS 15 |
| Hierarchical | Layers in one style at different opacities | .symbolRenderingMode(.hierarchical) | iOS 15 |
| Palette | A different style for each layer | .symbolRenderingMode(.palette) | iOS 15 |
| Multicolor | The symbol's own built-in colors | .symbolRenderingMode(.multicolor) | iOS 15 |
| Gradient | Layers filled with a linear gradient | .symbolColorRenderingMode(.gradient) | iOS 26 |
Hierarchical and palette both use foregroundStyle. Hierarchical needs one style; palette takes up to three, one per layer.
Image(systemName: "cloud.sun.rain.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
Image(systemName: "person.crop.circle.badge.plus")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .blue)
Image(systemName: "cloud.sun.rain.fill")
.symbolRenderingMode(.multicolor)
Gradient is technically a different kind of setting. It's a case of SymbolColorRenderingMode, whose other case is .flat, so it fills layers rather than choosing them. Apple's WWDC25 session says the gradient is generated from a single source color and works with every rendering mode, so you can combine it with any of the four.
Image(systemName: "flame.fill")
.symbolRenderingMode(.hierarchical)
.symbolColorRenderingMode(.gradient)
.foregroundStyle(.orange)
Apple says gradients render well at every size but stand out most in larger symbols, so use them where you want to bring out a symbol's depth.
Why do SF Symbols matter more on iPhone Duo?
On iPhone Duo, apps built with the iOS 27.1 SDK get a vertical bar along one side edge. Apple's Raise the bar with iPhone Duo Tech Talk describes its width as fixed, and @sarunw notes that this favors symbol-only items. Toolbar items with an icon move into the vertical bar; text-only items stay horizontal.
So a clear symbol for every toolbar action matters more than before. Pick symbols that read well without a label, and keep the title too, because the system still uses it in overflow menus. Our guide to the iPhone Duo vertical toolbar covers how items move, overflow and get pinned.
Apple's iPhone Duo talks don't say anything specific about symbol effects or rendering modes in the vertical bar. Nothing in this guide changes on the device, and the same modifiers apply. For how Liquid Glass groups and tints toolbar items in iOS 26, see our SwiftUI toolbar guide for Liquid Glass.
Key takeaways
- Animate symbols with
.symbolEffect: passvalue:for one-off effects andisActive:for effects that run until you stop them. - Bounce, Pulse, Variable Color, Scale, Appear and Disappear, and Replace need iOS 17. Wiggle, Rotate, Breathe and Magic Replace need iOS 18. Draw and Variable Draw need iOS 26.
- Use
.transition(.symbolEffect(...))for Appear, Disappear and Draw, and.contentTransition(.symbolEffect(.replace))to swap symbols. - Color symbols with monochrome, hierarchical, palette or multicolor, and add
.symbolColorRenderingMode(.gradient)on iOS 26. - On iPhone Duo, the vertical bar favors symbol-only items, so give every toolbar action a clear symbol and a title.
Next, read how iPhone Duo's vertical toolbar lays out those symbols, then see games that make real use of the fold on Built for the fold.
Frequently asked questions
How do I animate an SF Symbol in SwiftUI?
Apply .symbolEffect to an Image or a view that contains symbols. Pass value: for a one-off effect such as Bounce that plays whenever the value changes, or isActive: for an effect such as Pulse or Scale that runs until you turn it off.
Which iOS version added the SF Symbols Draw animation?
Draw On and Draw Off arrived in iOS 26 with SF Symbols 7. You apply them with .symbolEffect(.drawOn, isActive:) or as a transition, and they need symbols annotated for drawing.
What is the difference between hierarchical and palette rendering?
Hierarchical renders a symbol's layers in one color at different opacities. Palette lets you give each layer its own style by passing two or three styles to foregroundStyle.
How do I use the SF Symbols gradient in SwiftUI?
Apply .symbolColorRenderingMode(.gradient), available from iOS 26. It fills symbol layers with a linear gradient generated from a single color and works together with any of the four rendering modes.
What is Magic Replace?
Magic Replace, added in iOS 18, is a Replace transition between related symbols that keeps shared parts in place and animates only what changes, such as a slash or a badge. If the symbols aren't related, it falls back to a standard Replace.
Sources
- symbolEffect(_:options:value:) · Apple Developer Documentation
- symbolEffect(_:options:isActive:) · Apple Developer Documentation
- SymbolRenderingMode · Apple Developer Documentation
- symbolColorRenderingMode(_:) · Apple Developer Documentation
- symbolVariableValueMode(_:) · Apple Developer Documentation
- DrawOnSymbolEffect · Apple Developer Documentation
- magic(fallback:) · Apple Developer Documentation
- Animate symbols in your app · Apple Developer (WWDC23)
- What's new in SF Symbols 6 · Apple Developer (WWDC24)
- What's new in SF Symbols 7 · Apple Developer (WWDC25)
- Raise the bar with iPhone Duo · Apple Developer
- How to make SF Symbols draw themselves · Hacking with Swift
- There are 12 ways to animate SF Symbols in SwiftUI · @sarunw on X
- There are 5 ways to color SF Symbols in SwiftUI; gradient is new in iOS 26 · @sarunw on X
- A vertical bar's width is fixed, so it favors symbol-only items · @sarunw on X
Make the fold part of your app.
We showcase apps and games that do something real with iPhone Duo. Send us yours with a link to it on the App Store, or see what other developers have built.