A practical, step-by-step guide to adding, sizing, weighting, and coloring SF Symbols in SwiftUI using Image(systemName:), the most common way iOS developers display native icons.
By the end of this guide you will be comfortable placing any SF Symbol into a SwiftUI view and controlling its size, weight, scale, and color.
These are the everyday operations you reach for constantly when building real screens. They cover tab bar icons, button glyphs, list row accessories, and status indicators.
You need a Mac with Xcode and the free SF Symbols app installed. The SF Symbols app is where you find and copy the exact symbol names you will paste into code.
Nothing here requires third-party packages. Everything ships with the SwiftUI framework.
Open the SF Symbols app and use the search field to describe what you want, such as gear, trash, or star. The catalog is organized into categories on the left if you prefer to browse.
When you find a symbol, select it and copy its name. Names use lowercase words separated by dots, like `square.and.arrow.up` or `person.crop.circle`.
Many symbols have variants. A base name like `star` often has companions such as `star.fill`, `star.circle`, and `star.slash`, which you can see grouped in the app.
Verify the symbol's minimum OS availability in the app's inspector panel before committing to it. This avoids surprises on older deployment targets.
In SwiftUI, render a symbol with the system image initializer.
Image(systemName: "star.fill")
That is the entire baseline. The symbol appears at a default size derived from the surrounding context, already matching the system font metrics.
If you place it next to text in an `HStack`, it aligns to the text baseline automatically. You can also embed a symbol directly inside a `Label`, which pairs an icon with a title in one component.
Label("Favorite", systemImage: "star.fill")
This Label form is preferred for buttons, list rows, and toolbar items because the system decides how to present the icon and text per context.
Because symbols behave like text glyphs, the cleanest way to size them is with the `font` modifier. A symbol respects text styles like `.title`, `.headline`, and `.body`.
Image(systemName: "star.fill")
.font(.title)
Using a semantic text style is best for accessibility because the symbol scales with Dynamic Type when the user changes their text size.
For relative tuning, apply `imageScale`, which offers `.small`, `.medium`, and `.large`.
Image(systemName: "star.fill")
.imageScale(.large)
Reach for a fixed point size with `.font(.system(size:))` only when you genuinely need a specific dimension, since hard-coded sizes do not adapt to the user's text settings.
Symbol weight should usually mirror the weight of nearby text so the icon does not look too light or too heavy. You set weight through the font.
Image(systemName: "bolt.fill")
.font(.body.weight(.semibold))
Weights range from ultralight through black, the same scale San Francisco uses. Picking a weight near your label's weight keeps the pairing visually balanced.
Weight and scale work together. You can combine a semibold weight with a large scale to make an icon read as prominent without changing its point size.
Experiment in the SF Symbols app first. It lets you preview every weight and scale combination before you write the code.
The simplest coloring approach is `foregroundStyle`, which tints a monochrome symbol.
Image(systemName: "heart.fill")
.foregroundStyle(.red)
For richer looks, choose a rendering mode with `symbolRenderingMode`. Hierarchical applies opacity-based depth from a single color.
Image(systemName: "speaker.wave.3.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
Palette mode lets you pass multiple styles for symbols with distinct layers, and multicolor uses a symbol's own built-in colors.
Image(systemName: "cloud.sun.rain.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.gray, .yellow, .blue)
Not every symbol supports every mode meaningfully, so preview the symbol in the app to confirm it has the layers you expect.
Some symbols are designed to fill in stages, which suits signal strength, volume, or progress. You drive this with the variable value initializer.
Image(systemName: "wifi", variableValue: 0.6)
Pass a value between 0 and 1, and the symbol fills its variable layers proportionally. This gives you expressive status indicators without custom artwork.
This only works on symbols that include variable color layers. The SF Symbols app marks which symbols support variable color, so check there first.
Bind the value to your state, and the symbol updates as your data changes, which suits live download or connectivity meters.
Modern SwiftUI can animate symbols through the `symbolEffect` modifier, giving you feedback without shipping motion assets.
Image(systemName: "bell.fill")
.symbolEffect(.bounce, value: notificationCount)
Effects include bounce, pulse, scale, and others, and many can be triggered when a bound value changes. This is useful for confirming an action like adding to favorites.
These effects require sufficiently recent OS versions, so guard their use if you support older systems. The compiler and availability checks will guide you.
Keep motion purposeful. A subtle bounce on a meaningful event reads as polish, while constant animation reads as noise.
Do not hard-code point sizes everywhere. Favor semantic fonts so your icons scale with Dynamic Type and stay accessible.
Do not assume a symbol exists on every OS version. Always check availability in the app and provide a fallback for older targets.
Do not mismatch weight and scale with surrounding text. An icon that is much heavier or lighter than its label looks off, even if you cannot immediately say why.
Finally, remember that SF Symbols only solves iconography. Building, signing, and shipping the app still happens in Xcode and through the Apple Developer Program.
Symbols come into their own inside interactive controls. A `Button` can take a `Label`, and the system renders the icon appropriately for where the button lives.
Button {
save()
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
In a toolbar, the same `Label` may show as icon-only, while in a menu it shows icon and text together. You write the intent once and let the platform decide presentation.
This is why reaching for `Label` instead of a hand-built `HStack` pays off. You get context-appropriate rendering, correct spacing, and accessibility for free.
For a tab bar, attach the symbol through `tabItem` so each tab gets its standard icon-over-text layout without manual sizing.
Symbols inherit a lot of accessibility behavior, but you still owe users a meaningful description when an icon stands alone. A bare icon button with no label is invisible to VoiceOver.
Add an accessibility label when the symbol carries meaning on its own.
Image(systemName: "trash")
.accessibilityLabel("Delete")
When you use `Label` with visible text, the text already serves as the accessible description, so you usually do not need to add more.
Also prefer semantic fonts so symbols grow with Dynamic Type, and test with VoiceOver and the largest text sizes enabled. Accessible icons are part of shipping a polished iOS app, not an afterthought.
Use Image(systemName: "name"), for example Image(systemName: "star.fill"). For icon-and-text pairs, use Label("Title", systemImage: "name").
Prefer a semantic font like .font(.title) so it scales with Dynamic Type, or use .imageScale(.large). Use .font(.system(size:)) only when you need a fixed size.
Use .foregroundStyle for a single color, or set .symbolRenderingMode to hierarchical, palette, or multicolor for layered coloring.
Open the free SF Symbols app from Apple, search for the icon, and copy its name. The app also shows OS availability for each symbol.
Yes. On recent OS versions you can use the symbolEffect modifier for effects like bounce and pulse, often triggered by a changing value.