A semantic token names the job a color does, such as a title or a card's background, instead of the color itself. Every theme has the same 20 tokens, each with one stop for light mode and one for dark, so a color picked by its job, like theme.foregroundPrimary, switches with dark mode and keeps its contrast in any theme.
Four kinds of token#
Here's a booking card with every part labeled by the token that colors it. Switch to dark mode and every part moves to its dark stop:
Foreground · Text and icons
- 1foregroundPrimaryTitle and price, in light mode
- 2foregroundSecondaryDetails and host, in light mode
- 3foregroundTertiaryTotal and labels, in light mode
Background · Solid areas under text
- 4backgroundPrimaryPage, in light mode
- 5backgroundSecondaryCard, in light mode
- 6backgroundPrimaryPrice and date boxes, in light mode
- 7Color.backgroundPrimary + Color.proYellow._350 + Color.foregroundTertiaryRating pill, in light mode
- 8invertedBackgroundTertiary + invertedForegroundPrimaryHost avatar, in light mode
- 9invertedBackgroundSecondary + invertedForegroundPrimaryReserve button, in light mode
Surface · See-through layers
- 10surfacePrimaryPill over the photo, in light mode
Outline · Borders and dividers
- 11outlineTertiaryCard edge, in light mode
- 12outlineTertiaryDate divider, in light mode
- Foreground: text and icons, from
foregroundPrimaryfor titles and body text toforegroundTertiaryfor hints and labels. - Background: the solid areas text sits on: the page, cards, tags.
- Surface: see-through layers over other content, such as a pill on a photo, a sheet or a tooltip.
- Outline: edges and dividers.
outlinePrimarymarks a control's edge;outlineSecondaryandoutlineTertiaryare decorative lines.
Foreground, background and surface each have an inverted twin, for an area in the opposite mode: a filled button, an avatar, or a dark banner on a light screen.
All 20 tokens#
Drag across the themes to see each token's stop and Display P3 hex in any family. Surfaces let what's behind them show through, so they're drawn over each mode's page color.
| Color.proBlue.… | Light | Dark | Use for |
|---|---|---|---|
| Foreground · Text and icons | |||
| foregroundPrimary | _1000#0b1c32 | _50#eff6ff | Primary text, selected icons |
| foregroundSecondary | _800#254771 | _200#b8d7fe | Secondary text, unselected icons |
| foregroundTertiary | _700#335e94 | _300#8fc0fe | Tertiary text, disabled icons |
| invertedForegroundPrimary | _50#eff6ff | _1000#0b1c32 | Primary text on inverted backgrounds |
| invertedForegroundSecondary | _150#cbe1fe | _800#254771 | Secondary text on inverted backgrounds |
| invertedForegroundTertiary | _250#a4cbfe | _700#335e94 | Tertiary text on inverted backgrounds |
| Background · Solid areas under text | |||
| backgroundPrimary | _50#eff6ff | _1000#0b1c32 | Main content and app background |
| backgroundSecondary | _100#ddecff | _800#254771 | Secondary backgrounds, cards |
| backgroundTertiary | _200#b8d7fe | _700#335e94 | Tertiary backgrounds, modals |
| invertedBackgroundPrimary | _900#183150 | _50#eff6ff | Main background of inverted areas |
| invertedBackgroundSecondary | _800#254771 | _150#cbe1fe | Cards on inverted areas |
| invertedBackgroundTertiary | _650#3a69a5 | _250#a4cbfe | Modals on inverted areas |
| Surface · See-through layers | |||
| surfacePrimary | _200 · 50%#b8d7fe | _700 · 50%#335e94 | Cards and sheets over other content |
| surfaceSecondary | _200 · 30%#b8d7fe | _700 · 30%#335e94 | Panels and dialogs |
| surfaceTertiary | _200 · 10%#b8d7fe | _700 · 10%#335e94 | Tooltips and popovers |
| invertedSurfacePrimary | _400 · 40%#63a7fd | _600 · 40%#4176b8 | Cards and sheets on dark areas |
| invertedSurfaceSecondary | _400 · 20%#63a7fd | _600 · 20%#4176b8 | Panels and dialogs on dark areas |
| Outline · Borders and dividers | |||
| outlinePrimary | _600#4176b8 | _350#7ab4fd | Edges people need to see, like fields and checkboxes |
| outlineSecondary | _200#b8d7fe | _800#254771 | Decorative borders and dividers |
| outlineTertiary | _100#ddecff | _900#183150 | Decorative hairlines and subtle borders |
ColorTokens.swift also puts the 20 names on Color, built on gray, for parts of your app outside any theme. Color.backgroundPrimary is pure white and black.
Contrast of token pairs#
A stop has the same lightness in every hue, so a pair of tokens has the same contrast in every family, including your own. Here's every pair, drawn in proBlue, with its ratio in each mode:
passes WCAG AA: 4.5:1 for text, 3:1 for edges3:1, large text onlyfails
Keep foregrounds on backgrounds and inverted foregrounds on inverted backgrounds. Crossed over, text vanishes: foregroundPrimary on invertedBackgroundPrimary is 1.00:1 in dark mode.
Gray's ramp runs from white to black, so every gray pair passes AA. Over backgroundPrimary, every foreground token passes AA on every surface too. Over invertedBackgroundPrimary, invertedForegroundTertiary on invertedSurfacePrimary falls as low as 3.49:1.
outlinePrimary is for edges people need to find a control, like a text field's border, so it reaches 3:1 on every background token in both modes. outlineSecondary and outlineTertiary are for dividers and card edges, lines WCAG sets no minimum for, so they stay faint. Borders people need to see has an example.
Adding your own tokens#
The tokens live in ColorTokens.swift, a file you copy into your app, so you can rename them, move them or add your own. A token is a property on ProTheme with one stop for each mode. Keep its stops at least 12 from the background they sit on, and it passes AA in every hue by the distance rule:
public extension ProTheme {
/// Links in running text. Color(light:dark:) takes a Color for each mode,
/// so each stop gets toColor().
var accentText: Color {
// 5.11:1 on backgroundPrimary in light mode, 7.91:1 in dark
Color(light: _650.toColor(), dark: _350.toColor())
}
}
Text("See all trips")
.foregroundStyle(brand.accentText)
.padding()
.background(brand.backgroundPrimary) // _50 in light mode, _1000 in darkNext steps
- Using your first tokens Color text, buttons, cards and lists with tokens, then theme a movie card in one line.
- Managing dark mode Get dark mode without extra code: tokens switch stops for you, and your own colors can too.
- Setting up themes Recolor a view, a screen or your whole app from one value, with the same contrast in every hue.
- .contrastRatio(to:method:) Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.