Give the one thing that has to catch the eye, such as a price, an accent that stands apart from your brand with .complement. It turns your color or family halfway around the hue wheel and keeps its lightness, so the accent has the same contrast.
let accent = brand.complement // your brand's family, turned 180°
HStack(spacing: 16) {
VStack(alignment: .leading, spacing: 4) {
Text("Lisbon")
.font(.headline)
.foregroundStyle(brand.foregroundPrimary)
Text("3 nights from October 12")
.font(.subheadline)
.foregroundStyle(brand.foregroundSecondary)
}
Text("€480")
.padding(8)
.foregroundStyle(accent.foregroundPrimary) // _1000 in light mode, _50 in dark
.background(accent.backgroundTertiary) // _200 in light mode, _700 in dark
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(brand.backgroundPrimary) // _50 in light mode, _1000 in darkPick any color below, then set "See as" to "Lightness only": your color and its complement turn the same gray.
Every OKLCH hue at this color's lightness
let color = Color.proBlue._400.toColor()
color.complementAn accent from any token#
The Color version works on any color, tokens included, and keeps the token's job. The complement of a text token is a text color for both modes:
Text("Sold out")
.foregroundStyle(brand.foregroundTertiary.complement) // _700 in light mode, _300 in dark, at 347°Complements of the built-in families#
The built-in families sit every 10° around the wheel, so the complement of one is another. Color.proBlue sits at 250°, and its complement is Color.proAmber, at 70°:
let accent = Color.proBlue.complement // Color.proAmberHow it behaves#
complement turns the hue with rotateHue(by: .degrees(180)). A palette color lands on the same stop of the opposite hue, so its contrast doesn't change. Any other color keeps its lightness and as much chroma as Display P3 can show at the new hue.
On a ProTheme, you get a whole family at the stop you started from, so every token works on it. Gray has no hue, so its complement is gray.
complement returns only the turned color. harmony(.complement) returns both, yours first. Only hue tells the two apart, so they match in grayscale; when color carries meaning, add a label. Color theory covers when an accent this strong is the right pick.
API#
public extension Color {
var complement: Color { get }
}
public extension ProTheme {
var complement: ProTheme { get }
}It takes no parameters. On Color, it returns the opposite hue at the same lightness, in both light and dark mode. On ProTheme, it returns the family on the opposite side of the wheel, at this color's stop.
Sources#
- ColorTokensKit source: Color+Harmonies.swift and ProTheme+Harmonies.swift, which define
complement, and ColorHarmony.swift, which lists each harmony's hues.
See also
- .splitComplement(spread:) Two accents that stand apart from your brand, softer than its opposite, at the same contrast.
- .triad Secondary and tertiary colors for tags and categories, at the same contrast as your brand.
- .rotateHue(by:) Make an accent or a whole second theme from your brand color, at the same lightness.
- Color theory Pair stops that always read, and pick secondary hues that match your brand, in both modes.