3.4 — SF Symbols

Opening scenario

You need a “heart” icon for a like button. The designer sends a custom SVG. You import it, scale it, recolor it for light/dark, and ship. Two weeks later, the designer wants the heart to animate when tapped. You write a custom shape interpolation. Two weeks later, the designer wants the heart at the user’s accessibility text size. You write font-scaling math. Two weeks later, iOS 19 adds a beautiful new heart-fill animation built into the system… that you can’t use, because you committed to custom SVG.

You should have used SF Symbols from day one. This chapter shows you why and how.

AspectDetail
ToolSF Symbols app — free, Mac only
Library~6,900 symbols in SF Symbols 6 (2024)
FormatApple proprietary; rendered as a font
Custom symbolsSVG export from Figma → import to SF Symbols app
AnimationsymbolEffect modifier in SwiftUI

Concept → Why → How → Code

What SF Symbols actually is

SF Symbols is a typeface of icons. Each symbol is a glyph, sized and weighted to match SF Pro (Apple’s system font). When you render Image(systemName: "heart.fill"), you’re not loading an asset — you’re rendering text from a font file shipped with iOS.

Implications:

  • Symbols scale with .font() modifier exactly like text
  • Symbols inherit foregroundStyle exactly like text
  • Symbols pair perfectly with .body, .headline, etc. — they sit on the same baseline as adjacent text
  • Symbols cost zero asset weight (no PNGs in your bundle)
  • Symbols update with iOS — when iOS adds a new variant, your app gets it for free

Browsing and naming

Download the SF Symbols app from Apple. The app browser shows every symbol with its canonical name (heart, heart.fill, heart.circle, heart.circle.fill, heart.text.square).

The naming convention:

[concept].[variant].[shape].[fill/lines]

Examples:

  • heart → outline
  • heart.fill → filled
  • heart.circle → outline heart inside outline circle
  • heart.circle.fill → filled circle background
  • xmark.bin.fill → trash with X (compound concept)

When you can’t remember a name, open the SF Symbols app and search by keyword — “delete,” “user,” “send.”

Symbol variants

The same symbol comes in multiple style families. SwiftUI exposes these via .symbolVariant():

// Outline (default)
Image(systemName: "heart")

// Filled
Image(systemName: "heart").symbolVariant(.fill)
// or just
Image(systemName: "heart.fill")

// Slash (e.g. "muted")
Image(systemName: "speaker.slash")

// Circle
Image(systemName: "person.circle")

In a Label, variants propagate automatically — useful for tab bars where all icons should be filled when selected:

TabView {
    Tab("Home", systemImage: "house") { HomeView() }
    Tab("Profile", systemImage: "person") { ProfileView() }
}
.symbolVariant(.fill)   // all tab icons become filled

Rendering modes — the four colorings

let icon = Image(systemName: "cloud.sun.rain.fill")

// 1. Monochrome — single tint
icon.symbolRenderingMode(.monochrome).foregroundStyle(.blue)

// 2. Hierarchical — single tint, opacity layers (drops 100/50/25%)
icon.symbolRenderingMode(.hierarchical).foregroundStyle(.blue)

// 3. Palette — multiple distinct colors
icon.symbolRenderingMode(.palette)
    .foregroundStyle(.gray, .yellow, .blue)  // cloud, sun, rain

// 4. Multicolor — Apple's preset multicolor
icon.symbolRenderingMode(.multicolor)

Use hierarchical as the default for system UI — it’s the most legible across light/dark. Use palette when you need brand colors on a multipart symbol. Use multicolor sparingly for visual emphasis.

Symbol effects (iOS 17+)

Animations baked into the symbol itself:

@State private var liked = false

Image(systemName: liked ? "heart.fill" : "heart")
    .symbolEffect(.bounce, value: liked)
    .foregroundStyle(liked ? .red : .secondary)
    .onTapGesture { liked.toggle() }

The catalogue of effects (iOS 17+, expanded in 18):

EffectWhat it does
.bounceOne-time scale-up bounce
.pulseContinuous opacity pulse
.variableColorPer-layer color animation (great for activity indicators)
.scaleScale up/down on state change
.appear / .disappearAnimated mount/unmount
.replaceSmooth morph from one symbol to another (iOS 17+)
.wiggleSubtle wiggle (iOS 18+)
.breatheSlow breathing animation (iOS 18+)
.rotateRotation (iOS 18+)
// Replace effect: smoothly morph between symbols
Image(systemName: liked ? "heart.fill" : "heart")
    .contentTransition(.symbolEffect(.replace.byLayer))

These look professional, hit 60fps, and ship in a single line. Don’t roll your own.

Sizing and weight

Symbols inherit font sizing:

Image(systemName: "star.fill")
    .font(.title2)             // sets size
    .fontWeight(.bold)         // sets weight

// or together:
Image(systemName: "star.fill")
    .imageScale(.large)
    .symbolRenderingMode(.hierarchical)

imageScale (.small, .medium, .large) is a relative scale on top of the current font size. Combined with Dynamic Type, your icons scale automatically when the user bumps text size in Settings.

Custom SF Symbols

When you genuinely need a custom icon (brand mark, domain-specific glyph), draw it in Figma, export as SVG, then import into the SF Symbols app:

  1. Open SF Symbols app
  2. File → Export… any existing symbol as a template (e.g. heart.svg)
  3. Open the SVG in Figma; replace the path with your custom glyph (keep the canvas frame, baseline guides)
  4. Export back as SVG
  5. SF Symbols app → File → Open → your SVG → adjust by weight/scale → File → Export as .svg (Symbol)
  6. Drop the .svg into Xcode’s Asset Catalog (Symbol Asset, not Image Asset)
  7. Reference: Image(systemName: "my.custom.symbol") — yes, even custom symbols use systemName if dropped in Asset Catalog as a symbol

Custom symbols inherit all the rendering modes and effects. They are first-class citizens.

Accessibility

SF Symbols are automatically labeled by VoiceOver based on their name (“heart, filled”). Override when needed:

Image(systemName: "heart.fill")
    .accessibilityLabel("Add to favorites")

For decorative-only icons (e.g. inline with a labeled button), mark them:

Button {
    save()
} label: {
    Label("Save", systemImage: "tray.and.arrow.down")
}
// → Label handles accessibility: VoiceOver reads "Save, button"
// → The image is decorative

In the wild

  • Apple Mail is SF Symbols top to bottom — sidebar icons, toolbar buttons, swipe action icons. Open it as the reference implementation.
  • Apple Health mixes SF Symbols (system actions) with custom symbol assets (specific organs, vitals) — all using Image(systemName:).
  • Cash App uses custom SF Symbols for its dollar-sign branding alongside system symbols in nav.
  • Notion for iOS uses SF Symbols across almost every UI surface — their migration from custom PNGs to SF Symbols reportedly saved ~3 MB of app size.

Common misconceptions

  1. “SF Symbols are too generic — they make my app look like every other app.” That’s the point of system controls. Use SF Symbols for system-task icons (back, share, settings) and reserve custom illustration for hero/marketing surfaces.
  2. “SF Symbols can’t be colored.” They absolutely can — see palette and multicolor rendering modes.
  3. “I need to ship PDF/PNG fallbacks for older iOS.” SF Symbols ship back to iOS 13. If you’re targeting iOS 15+, you can use any symbol introduced in SF Symbols 4 (2022). Filter the SF Symbols app by deployment target.
  4. “Custom SF Symbols are too much work.” A one-time 30-minute setup per custom glyph. After that, free animations, free Dark Mode, free Dynamic Type. Cheaper than rolling your own image system.
  5. “Symbol effects are eye candy I should turn off for performance.” They’re GPU-accelerated and cheap. Use them — they’re a free quality signal.

Seasoned engineer’s take

The single biggest delta between an app that feels Apple-native and one that feels third-party is how the icons are handled. Use SF Symbols. Use the rendering modes. Use the symbol effects. The reviewer who would have given you 4 stars gives 5 because the heart bounces correctly.

When a designer hands you a custom icon for a system task (back, settings, share, send), push back: “Is there a reason we’re not using the system SF Symbol for this?” Often the answer is “I forgot SF Symbols had one” — and you save yourself an export pipeline.

Keep an eye on each year’s WWDC for new SF Symbols. Apple adds 500+ per release. Symbols you wanted three years ago may now exist.

TIP: SF Symbols app → Sample tab — lets you preview symbols with custom text size, weight, color, and rendering mode side by side. Use it before committing to a symbol.

WARNING: Don’t use SF Symbols outside Apple platforms (no Android, no web) unless you own a license — Apple’s terms restrict use. For cross-platform brand symbols, draw custom.

Interview corner

Junior-level: “What is SF Symbols?”

Apple’s icon system — a glyph font shipping with every Apple OS, accessible via Image(systemName:). Around 6,900 symbols, free, animatable, scale with Dynamic Type.

Mid-level: “What’s the difference between hierarchical and palette rendering modes?”

Hierarchical uses one tint at varying opacity to imply layers (single color brand-friendly). Palette uses multiple distinct foregroundStyles, one per layer of the symbol — useful when symbol parts represent different semantic meanings (cloud=gray, sun=yellow).

Senior-level: “Your designer wants a custom logo as an icon. Walk me through your pipeline.”

Draw in Figma at SF Symbols template grid sizes. Export as SVG. Import to SF Symbols app, align baselines and weight axes. Export as .svg symbol asset. Drop into Xcode Asset Catalog as Symbol. Use via Image(systemName:). Custom symbols inherit .symbolRenderingMode, Dynamic Type, and color modifiers — write once, ship to every accessibility setting.

Red flag in candidates: Reaching for PNG sprite sheets or custom font rendering for icons in 2025. Means they haven’t kept up with the platform.

Lab preview

You’ll consume SF Symbols extensively in Lab 3.1 — Figma to SwiftUI and audit incorrect icon usage in Lab 3.2.


Next: 3.5 — Adaptive design: Dark Mode & Dynamic Type