Human Interface Guidelines and Theming
Apple's Human Interface Guidelines (HIG) describe how apps should look, feel, and behave to be consistent with the rest of the platform — spacing conventions, typography scale, color semantics, navigation patterns, and accessibility expectations. SwiftUI was designed hand-in-hand with the HIG, so a large fraction of 'doing the right thing' is simply using SwiftUI's built-in system colors, fonts, and controls instead of hardcoding pixel values or custom colors. Theming in SwiftUI means adapting your app's appearance to system settings — light/dark mode, accessibility text sizes, high contrast, and reduced motion — without maintaining separate implementations for each variant.
Cricket analogy: Just as every international ground follows ICC pitch and boundary regulations so a Test match at Lord's feels consistent with one at the MCG, the HIG defines spacing and typography rules so every iOS app feels consistent with the rest of the platform.
struct ProfileCard: View {
let name: String
let bio: String
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(name)
.font(.headline)
.foregroundStyle(.primary)
Text(bio)
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding()
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
.frame(maxWidth: .infinity, alignment: .leading)
}
}Semantic colors over hardcoded values
SwiftUI ships semantic colors like .primary, .secondary, Color(.systemBackground), and material styles such as .regularMaterial and .thinMaterial. These automatically invert or adjust between light and dark mode and respect increased-contrast accessibility settings. Hardcoding Color(red: 0.1, green: 0.1, blue: 0.1) for 'dark text' breaks in dark mode, where the background may also be dark, producing invisible text. When you do need brand colors, define them in an asset catalog color set with both a light and dark appearance rather than as a literal RGB value in code, so the same Color('BrandPrimary') reference adapts automatically.
Cricket analogy: Using .primary and .secondary text colors is like a stadium scoreboard that automatically re-tints its digits for day or floodlit night matches, whereas hardcoding a dark gray for text is like painting numbers that vanish once the lights dim.
Typography and Dynamic Type
The HIG asks apps to support Dynamic Type so users who need larger text aren't excluded. SwiftUI's font styles — .largeTitle, .title, .headline, .body, .caption, and so on — are all Dynamic Type-aware out of the box: they scale automatically when the user changes their preferred text size in Settings. Using .font(.system(size: 17)) opts out of this scaling and should be reserved for cases where a fixed size is genuinely required, such as a glyph-sized icon. Layouts built with stacks and flexible frames tend to reflow gracefully as text grows; fixed-size containers do not, so test your UI at the largest accessibility text sizes, not just the default.
Cricket analogy: Just as Dynamic Type scales body text when a user increases their preferred size, a stadium's electronic scoreboard uses a font size chosen to remain legible from the cheapest seats, not a fixed tiny size that only works up close.
Dark mode and appearance-aware assets
Dark mode isn't just inverted colors — the HIG recommends using slightly desaturated, lower-contrast tones in dark mode to reduce eye strain in low light, which is why Apple's system colors don't simply flip 0↔255. To make images adapt, add both an 'Any Appearance' and 'Dark' variant to an image set in the asset catalog, or use SF Symbols, which automatically render in the correct color and weight for the current appearance and Dynamic Type size without any extra work.
Cricket analogy: Floodlit day-night Test matches use a pink ball with muted, lower-glare seams rather than a simple color inversion of the red ball, just as dark mode uses desaturated tones rather than literally flipping every RGB value.
A useful mental model: theming in SwiftUI is less about 'switching color palettes' and more about describing intent (primary text, secondary text, a card background) and letting the system resolve that intent into the right concrete value for the current appearance, contrast setting, and platform.
A common mistake is testing an app only in light mode with default text size, shipping it, and discovering later that custom-colored text is unreadable in dark mode or that a fixed-height row clips text at accessibility sizes. Preview canvases support toggling color scheme and Dynamic Type category — use that during development, not just at the end.
Respecting system-wide accessibility settings
Beyond Dynamic Type and dark mode, the HIG calls out reduced motion, reduced transparency, and increased contrast. SwiftUI exposes these via environment values like @Environment(\.accessibilityReduceMotion) and @Environment(\.accessibilityReduceTransparency), which you can read to conditionally simplify animations or swap a material background for a solid one. Respecting these is not optional polish — for some users it determines whether the app is usable at all.
Cricket analogy: A visually sensitive spectator asking the stadium to dim strobing scoreboard animations during a wicket celebration mirrors @Environment(\.accessibilityReduceMotion), letting the app simplify or remove flashy transitions when requested.
- Use semantic colors and materials instead of hardcoded RGB values so appearance adapts to light/dark mode automatically.
- SwiftUI's built-in font styles are Dynamic Type-aware; fixed .system(size:) fonts opt out of that scaling.
- Dark mode uses deliberately desaturated tones, not a naive color inversion.
- SF Symbols automatically adapt color, weight, and scale to the current appearance and Dynamic Type size.
- Environment values like accessibilityReduceMotion let you honor system accessibility preferences.
- Preview and test layouts at large accessibility text sizes and in both color schemes throughout development.
Practice what you learned
1. Why is Color(red: 0.1, green: 0.1, blue: 0.1) generally discouraged for text color in a SwiftUI app that supports dark mode?
2. What happens when you use .font(.system(size: 17)) instead of a semantic style like .body?
3. Why doesn't dark mode simply invert each light-mode color?
4. What is a key advantage of using SF Symbols for iconography?
5. Which environment value would you read to simplify or disable animations for users who have enabled a system accessibility setting?
Was this page helpful?
You May Also Like
Text and Typography in SwiftUI
How the Text view handles fonts, Dynamic Type, styling, and localization to produce accessible, readable typography.
Modifiers in SwiftUI
How view modifiers work under the hood, why order matters, and how chained modifiers build up new wrapped view types.
Views and the View Protocol
How the `View` protocol works, what `body` must return, and how SwiftUI composes small views into larger interfaces.
Common iOS & SwiftUI Pitfalls
Frequent mistakes SwiftUI developers make around state ownership, list identity, concurrency, and view identity, with clear explanations of why they break.