Pick a theme by name, such as Color.proBlue, and your view gets 20 stops and every token, in light and dark mode. There are 37 names: 36 hues, one every 10°, and proGray. Each is a ProTheme.
let theme = Color.proBlue
Text("Lisbon")
.font(.headline)
.foregroundStyle(theme.foregroundPrimary) // _1000 in light mode, _50 in dark
.padding()
.background(theme.backgroundSecondary) // _100 in light mode, _800 in darkHere are all 37, each from light to dark:
Giving a view a theme#
Pass the theme in and draw with its tokens. Then one name changes the whole view, and the contrast stays the same:
struct TripCard: View {
let theme: ProTheme
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text("Lisbon")
.font(.headline)
.foregroundStyle(theme.foregroundPrimary)
Text("3 nights from October 12")
.foregroundStyle(theme.foregroundSecondary)
}
.padding()
.background(theme.backgroundSecondary)
}
}
TripCard(theme: Color.proGold)
TripCard(theme: Color.proPink)Letting people choose a theme#
Color.allProHues holds every theme by its name without the pro, so a setting can store a plain string:
struct ThemeSetting: View {
@AppStorage("theme") private var name = "Blue"
var body: some View {
Picker("Theme", selection: $name) {
ForEach(Color.allProHues.keys.sorted(), id: \.self) { option in
Text(option)
}
}
}
}
let saved = UserDefaults.standard.string(forKey: "theme") ?? "Blue"
let theme = Color.allProHues[saved] ?? Color.proBlueA dictionary has no order, so sort the names for a list. The fallback covers a name that's no longer there.
How it behaves#
Each name is ProTheme.primary(forHue:) at its hue: Color.proBlue is hue 250. Its own color is its _450 stop, and its tokens come from ColorTokens.swift.
Every hue has the same lightness and chroma at each stop, so swapping one name for another keeps your contrast. proGray has no hue and a lightness ladder of its own, and the tokens ColorTokens.swift puts on Color, such as Color.foregroundPrimary, are built on it.
Each name is a computed property, but the ramps behind it are built once and cached, so reading Color.proBlue in a view's body is cheap. ProTheme is Sendable, so you can pass themes between threads.
Older versions placed some names a few degrees off this grid, so after updating, a theme can look a little different. Troubleshooting lists what to check.
API#
public extension Color {
static var proGray: ProTheme { get }
static var proPink: ProTheme { get } // hue 0°, then one every 10°, to proRose at 350°
static var allProHues: [String: ProTheme] { get }
}| Hue | Theme | Hue | Theme | Hue | Theme |
|---|---|---|---|---|---|
| 0° | proPink | 120° | proChartreuse | 240° | proAzure |
| 10° | proRuby | 130° | proLime | 250° | proBlue |
| 20° | proRed | 140° | proGrass | 260° | proCobalt |
| 30° | proTomato | 150° | proGreen | 270° | proIndigo |
| 40° | proCoral | 160° | proJade | 280° | proIris |
| 50° | proOrange | 170° | proEmerald | 290° | proViolet |
| 60° | proBrown | 180° | proMint | 300° | proGrape |
| 70° | proAmber | 190° | proTeal | 310° | proPurple |
| 80° | proGold | 200° | proTurquoise | 320° | proOrchid |
| 90° | proMustard | 210° | proCyan | 330° | proPlum |
| 100° | proYellow | 220° | proCerulean | 340° | proMagenta |
| 110° | proOlive | 230° | proSky | 350° | proRose |
Each property returns a ProTheme whose own color is its _450 stop. allProHues maps each of the 37 names, such as "Blue" or "Gray", to the same value.
Sources#
- The library's Color+ProColors.swift, which defines every name, its hue and
allProHues.
See also
- ProTheme Make a theme from a hex, a hue or OKLCH numbers, and every stop and token keeps its contrast.
- Stops: ._50 to ._1000 Twenty fixed colors per theme, lightest to darkest, with the same contrast in every hue.
- What does the library offer? 36 color themes, ready-to-use tokens with dark mode, a theme for your brand, and state functions.
- Understanding semantic tokens Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.