A palette color
A color near it, off the palette
Move the slider. proBlue._600 jumps from stop to stop, and #3C80C4, a similar blue that isn't on the palette, moves by the same steps but stays between them.
A color function like lighten(by:) moves a palette stop by whole stops. A token is a stop in each mode, so a color you derive from one is a real stop of your theme too, with a contrast you know before you run the app:
theme.foregroundTertiary.soften() // exactly _650 in light mode, _350 in dark
theme.foregroundTertiary.lighten(by: 3) // exactly _550 and _150
theme.foregroundTertiary.rotateHue(by: .degrees(32)) // _700 and _300 of the ramp 32° around the wheel
theme.backgroundTertiary.invert() // the mirrored stops, _850 and _350At the ends, the stops hold: _50 doesn't get any lighter.
Colors off the palette#
let nearby = Color(hex: "#3C80C4") // between _550 and _600
nearby.lighten() // #498CD1
nearby.lighten(by: 2) // #5599DF
nearby.darken() // #2F74B7Any other color moves along the same ladder, continuously, so one stop lighter is the same step for every color. It keeps its hue and as much chroma as the screen can show. A gray moves on gray's own ladder.
Recognizing a palette color#
A color counts as a stop when its CIELab L* is within 0.6 of the stop's, and its chroma within 0.006 of what that stop's ramp gives its hue. That's enough to cover rounding in hex values, and too tight to match two stops.
A color with a chroma of 0.005 or less has no visible hue, so it's matched against gray's stops instead: proGray._400.lighten() is exactly proGray._350.
Functions that snap#
lighten, darken, soften, strengthen, tints, shades, invert and monochromatic land a palette color on stops, and rotateHue and the harmonies keep its stop at the new hue. saturate, desaturate and blend leave the palette on purpose. saturate and desaturate still keep L*, and with it contrast, as fitting colors into Display P3 explains.
The darkest gray#
Gray _1000 isn't recognized. Its hex, #000001, has a chroma just over the cutoff for gray, so proGray._1000.lighten() gives #0e1112 rather than gray _950, and so does lightening Color.white.invert(). Use Color.proGray._950 directly.
Sources#
- ColorTokensKit source, PaletteStop.swift,
StopLadder.swiftandColorAdjustment.swift.
See also
- Building for interaction states Give buttons and rows hover, pressed, selected and disabled colors that stay readable in both modes.
- How the ramps are built The lightness and chroma behind every stop, so any hue you pick gives the same contrast.
- What are tokens? Colors picked one at a time never line up. Tokens name each job and pick its color in both modes.
- .contrastRatio(to:method:) Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.