See a color the way the palette does with toOKLCH(): lightness, chroma and hue in OKLCH, the space every ramp is built in. It takes nothing and returns an OKLCHColor you can read, change and turn back into a Color:
let brandColor = Color(hex: "#00B386")
let oklch = brandColor.toOKLCH() // OKLCHColor(l: 0.680, c: 0.139, h: 166.99)
oklch.h // 166.99, the hue ProTheme(hex:) builds the brand's theme onType any hex value to get it in every color type, with the Swift that makes it and the nearest palette stop:
L* 52.3 · luminance 0.204 · nearest stop proBlue._550 (ΔEOK 0.019)
| Hex | #3c80c4 | Color(hex: "#3C80C4") |
|---|---|---|
| RGB | 0.235 0.502 0.769 | RGBColor(r: 0.235, g: 0.502, b: 0.769, alpha: 1) |
| HSL | 210° 53.5% 50.2% | Color(h: 210, s: 0.535, l: 0.502) |
| OKLCH | 0.5879 0.1253 250.8 | OKLCHColor(l: 0.5879, c: 0.1253, h: 250.8) |
| OKLab | 0.5879 -0.0412 -0.1183 | OKLabColor(l: 0.5879, a: -0.0412, b: -0.1183) |
| LCH | 52.27 41.7 271.18 | LCHColor(l: 52.27, c: 41.7, h: 271.18) |
| LAB | 52.27 0.86 -41.69 | LABColor(l: 52.27, a: 0.86, b: -41.69, alpha: 1) |
| XYZ | 0.1954 0.2038 0.5512 | XYZColor(x: 0.1954, y: 0.2038, z: 0.5512, alpha: 1) |
Choosing a color space#
- OKLCH for how colors look: hue stays put as chroma changes, so the palette and the color functions use it.
- CIELab L* for contrast: it depends only on luminance, as WCAG contrast does, so the palette holds it equal at each stop.
.toLCH()and.toLAB()give it. - RGB for handing colors to other code, with
.toRGB(), and hex for colors inside sRGB, with.getHexString().
Why OKLCH? shows the difference.
Making an OKLCHColor#
OKLCHColor(l: 0.68, c: 0.14, h: 167) // #00B386
OKLCHColor(hex: "#00B386")
OKLCHColor(oklchString: "oklch(0.68 0.14 167)")Write oklchString with three plain numbers: no percent signs, no alpha, and 0.68 rather than .68. A string it can't read gives a default color, #E7729B, instead of an error. OKLCHColor matches CSS oklch() numbers to within rounding.
Changing one value#
getColor(l:c:h:alpha:) replaces the values you pass and returns a Color:
let oklch = Color(hex: "#00B386").toOKLCH()
let quieter = oklch.getColor(c: 0.05) // same lightness and hue, closer to grayIt doesn't fit the result into Display P3 as the color functions do, so for more chroma, use saturate(by:).
The ramp at this hue#
_50 to _1000, allStops and getColor(at:) read the ramp of the color's own hue, and the static getPrimaryColor(forHue:isGrayscale:) gives a hue's _450. They return OKLCHColor values; for a theme with every token, use ProTheme(hex:) or ProTheme.primary(forHue:).
How it behaves#
Conversions between the CIELab and OK types pass through extended sRGB without clipping, so a Display P3 color comes through unchanged. A token has two values, and toOKLCH() reads one: on iOS 17, macOS 14 and later, the light-mode value. To read dark mode, convert the stop the token uses there.
Hues are stored wrapped to 0 to 360 and rounded to 0.01°, so OKLCHColor(l: 0.5, c: 0.1, h: 370).h is 10. toColor() and getColor(l:c:h:alpha:) always give an opaque color.
From an OKLCHColor you can go to toRGB(), toLCH(), toOKLab() and toColor().
API#
public extension Color {
func toOKLCH() -> OKLCHColor
}
public struct OKLCHColor: Hashable, Sendable {
public let l: CGFloat // 0 to 1
public let c: CGFloat // 0 to 0.32 inside sRGB, more in Display P3
public let h: CGFloat // 0 to 360
public let alpha: CGFloat // 0 to 1
public init(l: CGFloat = 0, c: CGFloat = 0, h: CGFloat = 0, alpha: CGFloat = 1)
public init(color: Color)
public init(hex: String)
public init(oklchString: String)
public func getColor(
l: CGFloat? = nil, c: CGFloat? = nil, h: CGFloat? = nil, alpha: CGFloat? = nil
) -> Color
public func getColor(at index: Int) -> OKLCHColor
public var allStops: [OKLCHColor] { get }
public var _50: OKLCHColor { get } // and every stop to _1000
public static func getPrimaryColor(forHue hue: Double, isGrayscale: Bool = false) -> OKLCHColor
public func lerp(_ other: OKLCHColor, t: CGFloat) -> OKLCHColor
public func toRGB() -> RGBColor
public func toLCH() -> LCHColor
public func toOKLab() -> OKLabColor
public func toColor() -> Color
}toOKLCH() takes no parameters. ProTheme has it too, as in brand._600.toOKLCH().
Sources#
- Color+ColorSpaces.swift, which defines
toOKLCH()onColor. - The OKLCH folder, which defines
OKLCHColor, its string parser and its stops. - Björn Ottosson, A perceptual color space for image processing (2020), which introduces OKLab and OKLCH.
- W3C, CSS Color Module Level 4, which defines
oklch().
See also
- Why OKLCH? Every hue looks evenly matched at each stop, so you can swap families without rechecking contrast.
- .toOKLab() Get a color in OKLab, where straight-line math between colors looks even, for your own blends.
- Color(hex:) Turn a hex value from your design file into a SwiftUI color, with or without transparency.
- ProTheme Make a theme from a hex, a hue or OKLCH numbers, and every stop and token keeps its contrast.
- .normalizedHue and .rounded(to:) Wrap any angle into 0° to 360° and round numbers the way the library stores every hue.