Skip to content
ColorTokensKitby Penguin Design Ventures
Contents

Open source · SwiftUI · Agent-friendly

The most complete color system for Swift. By designers, for engineers and agents.

No hex codes to remember, no dark-mode pairs to hand-pick, no asset catalog to keep in sync.

From day one your app looks like a designer went through every screen. Text stays readable, dark mode is already done, buttons look right when pressed or disabled, and a rebrand is a one-line change. You choose one color and never open a picker again, and the names in your code are the ones your designer and your coding agent use too.

colors
740
themes
37
tokens
20
APIs
40
components
29
pages
71
Palette / All 37 families × 20 stops
50
100
150
200
250
300
350
400
450
500
550
600
650
700
750
800
850
900
950
1000
proGray
proPink
proRuby
proRed
proTomato
proCoral
proOrange
proBrown
proAmber
proGold
proMustard
proYellow
proOlive
proChartreuse
proLime
proGrass
proGreen
proJade
proEmerald
proMint
proTeal
proTurquoise
proCyan
proCerulean
proSky
proAzure
proBlue
proCobalt
proIndigo
proIris
proViolet
proGrape
proPurple
proOrchid
proPlum
proMagenta
proRose
proBlue._500#508edc
OKLCH
0.639 0.15 250
L* (CIELab)
58.2
Family hue
250°
vs white
3.37:1
vs black
6.23:1
swift
Color.proBlue._500.toColor()

How it works#

HueproOrange sits at 50° of 36 hues
Ramp20 stops, from _50 to _1000
_800_200
StopsEach one a fixed color
lightdark
JobforegroundSecondary: a stop for each mode
Lisbon3 nights
Lisbon3 nights
Your appSecondary text, in both modes

Every hue becomes a ramp of 20 stops, and all 36 hues share one lightness at each stop. A token names a job, like secondary text, and picks one stop for light mode and one for dark. Your views use tokens, so they follow dark mode and any theme on their own. What are tokens? walks through it.

What you get#

A palette of well-defined colors#

We give you 37 families of 20 stops, the 740 colors at the top of this page. Every hue has the same lightness at each stop, so _600 means the same contrast in red as in blue. Many colors reach past sRGB into Display P3, the wider range of color an iPhone screen shows, and any hue you like, such as your brand's, can become a family of its own. What does the library offer? shows every stop.

Automatic dark mode#

swift
let brand = ProTheme(hex: "#00B386")

HStack {
    VStack(alignment: .leading, spacing: 4) {
        Text("LIS → KIX")
            .font(.headline)
            .foregroundStyle(brand.foregroundPrimary)     // _1000 in light mode, _50 in dark
        Text("Oct 12 · 14h 20m")
            .font(.subheadline)
            .foregroundStyle(brand.foregroundSecondary)   // _800 in light mode, _200 in dark
    }
    Spacer()
    Text("€620")
        .foregroundStyle(brand.foregroundPrimary)
}
.padding()
.background(brand.backgroundPrimary)                      // _50 in light mode, _1000 in dark
light mode
LIS → KIXOct 12 · 14h 20m
€620

A token names the job a color does and picks a stop for each mode. We give you 20 of them, tested in production, so you never write a dark-mode color by hand. What are tokens? explains how they work.

Built-in theming#

swift
struct TripCard: View {
    let city: String
    let price: String
    let theme: ProTheme

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: "airplane")
                .foregroundStyle(theme.foregroundSecondary)
                .frame(width: 44, height: 44)
                .background(theme.backgroundTertiary, in: .rect(cornerRadius: 12))
            VStack(alignment: .leading, spacing: 2) {
                Text(city)
                    .font(.headline)
                    .foregroundStyle(theme.foregroundPrimary)
                Text("3 nights from October 12")
                    .font(.subheadline)
                    .foregroundStyle(theme.foregroundSecondary)
            }
            Spacer()
            Text(price)
                .font(.subheadline.weight(.semibold))
                .foregroundStyle(theme.foregroundPrimary)
                .padding(.horizontal, 10)
                .padding(.vertical, 4)
                .background(theme.backgroundPrimary, in: .capsule)
            Image(systemName: "chevron.right")
                .foregroundStyle(theme.foregroundTertiary)
        }
        .padding(12)
        .background(theme.backgroundSecondary, in: .rect(cornerRadius: 16))
    }
}

VStack(spacing: 10) {
    TripCard(city: "Lisbon", price: "€420", theme: Color.proOrange)
    TripCard(city: "Kyoto", price: "€980", theme: Color.proPink)
    TripCard(city: "Reykjavík", price: "€640", theme: Color.proSky)
}
light mode
Lisbon3 nights from October 12
€420
Kyoto3 nights from October 12
€980
Reykjavík3 nights from October 12
€640

Every family has the same tokens at the same contrast, so a view that takes a theme can wear any of them, in one line. Setting up themes shows how far that goes.

Accessibility from day one#

Before: SwiftUI's system colors
Lisbon
Color.red, 3.57:1
Lisbon
Color.green, 2.22:1
Lisbon
Color.blue, 3.52:1
After: ColorTokensKit, stop _600
Lisbon
proRed._600, 4.68:1
Lisbon
proGreen._600, 4.68:1
Lisbon
proBlue._600, 4.68:1

SwiftUI's system colors are tuned one at a time, so as text on white, none of the three above reaches the 4.5:1 that WCAG AA asks for body text. Ours share one lightness at each stop, so _600 passes in every hue. Every text token passes AA on the page background, in both modes, and for stops you pick yourself, 12 stops apart always reaches 4.5:1. How accessible is it? lists what's guaranteed, and why use ColorTokensKit over native colors? compares the two in full.

Functions for interaction states#

swift
let fill = brand.invertedBackgroundTertiary   // _650 in light mode, _250 in dark

HStack {
    Text("Book now").padding(12).background(fill)
    Text("Book now").padding(12).background(fill.darken(by: 2))       // pressed: _750 in light mode, _350 in dark
    Text("Sold out").padding(12).background(fill.desaturate(by: 1))   // disabled: gray at the same lightness
}
.foregroundStyle(brand.invertedForegroundPrimary)
light mode
Book nowBook nowSold out

Hover, pressed and disabled colors come from the color you already have, so you don't pick a new color for each state, and "Sold out" keeps the button's contrast. Building for interaction states shows every state, and darken(by:) and desaturate(by:) have the details.

Next steps

  • What are tokens? Colors picked one at a time never line up. Tokens name each job and pick its color in both modes.
  • Installing ColorTokensKit Add one package and one file of tokens, and every view gets the palette and dark mode.
  • Using your first tokens Color text, buttons, cards and lists with tokens, then theme a movie card in one line.
  • Components and examples Copy the common mobile components, from buttons to paywalls, each built from tokens for both modes.