Skip to content
ColorTokensKitby Penguin Design Ventures
Contents
Explanation

Understanding semantic tokens

Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.

Updated View as Markdown

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:

Try it / A booking card, token by token
Mode

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 foregroundPrimary for titles and body text to foregroundTertiary for 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. outlinePrimary marks a control's edge; outlineSecondary and outlineTertiary are 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.

Try it / Semantic tokens
Theme · drag acrossproBlue
proGrayproRose
Color.proBlue.…LightDarkUse for
Foreground · Text and icons
foregroundPrimary_1000#0b1c32_50#eff6ffPrimary text, selected icons
foregroundSecondary_800#254771_200#b8d7feSecondary text, unselected icons
foregroundTertiary_700#335e94_300#8fc0feTertiary text, disabled icons
invertedForegroundPrimary_50#eff6ff_1000#0b1c32Primary text on inverted backgrounds
invertedForegroundSecondary_150#cbe1fe_800#254771Secondary text on inverted backgrounds
invertedForegroundTertiary_250#a4cbfe_700#335e94Tertiary text on inverted backgrounds
Background · Solid areas under text
backgroundPrimary_50#eff6ff_1000#0b1c32Main content and app background
backgroundSecondary_100#ddecff_800#254771Secondary backgrounds, cards
backgroundTertiary_200#b8d7fe_700#335e94Tertiary backgrounds, modals
invertedBackgroundPrimary_900#183150_50#eff6ffMain background of inverted areas
invertedBackgroundSecondary_800#254771_150#cbe1feCards on inverted areas
invertedBackgroundTertiary_650#3a69a5_250#a4cbfeModals on inverted areas
Surface · See-through layers
surfacePrimary_200 · 50%#b8d7fe_700 · 50%#335e94Cards and sheets over other content
surfaceSecondary_200 · 30%#b8d7fe_700 · 30%#335e94Panels and dialogs
surfaceTertiary_200 · 10%#b8d7fe_700 · 10%#335e94Tooltips and popovers
invertedSurfacePrimary_400 · 40%#63a7fd_600 · 40%#4176b8Cards and sheets on dark areas
invertedSurfaceSecondary_400 · 20%#63a7fd_600 · 20%#4176b8Panels and dialogs on dark areas
Outline · Borders and dividers
outlinePrimary_600#4176b8_350#7ab4fdEdges people need to see, like fields and checkboxes
outlineSecondary_200#b8d7fe_800#254771Decorative borders and dividers
outlineTertiary_100#ddecff_900#183150Decorative 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:

Try it / Token pairs in Color.proBlue
Pairs
lightdark
foregroundPrimaryon backgroundPrimary15.64:1 · passes AA
foregroundPrimaryon backgroundPrimary15.64:1 · passes AA
foregroundPrimaryon backgroundSecondary14.19:1 · passes AA
foregroundPrimaryon backgroundSecondary8.72:1 · passes AA
foregroundPrimaryon backgroundTertiary11.48:1 · passes AA
foregroundPrimaryon backgroundTertiary6.10:1 · passes AA
foregroundSecondaryon backgroundPrimary8.72:1 · passes AA
foregroundSecondaryon backgroundPrimary11.48:1 · passes AA
foregroundSecondaryon backgroundSecondary7.91:1 · passes AA
foregroundSecondaryon backgroundSecondary6.40:1 · passes AA
foregroundSecondaryon backgroundTertiary6.40:1 · passes AA
foregroundSecondaryon backgroundTertiary4.48:1 · large text only
foregroundTertiaryon backgroundPrimary6.10:1 · passes AA
foregroundTertiaryon backgroundPrimary9.00:1 · passes AA
foregroundTertiaryon backgroundSecondary5.53:1 · passes AA
foregroundTertiaryon backgroundSecondary5.02:1 · passes AA
foregroundTertiaryon backgroundTertiary4.48:1 · large text only
foregroundTertiaryon backgroundTertiary3.51:1 · large text only

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:

swift
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 dark
light mode
See all trips

Next 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.