Skip to content
ColorTokensKitby Penguin Design Ventures
Contents
Function

.toOKLab()

Get a color in OKLab, where straight-line math between colors looks even, for your own blends.

Updated View as Markdown

Do your own color math in OKLab with toOKLab(): lightness plus two axes, green to red (a) and blue to yellow (b), spaced so equal steps look equal. It takes nothing and returns an OKLabColor:

swift
Color(hex: "#00B386").toOKLab()   // OKLabColor(l: 0.680, a: -0.136, b: 0.031)

Mixing in a straight line#

OKLab mixes two colors along a straight line, so the middle can be a little grayer than either end. OKLCH turns around the hue wheel instead and keeps chroma up:

swift
let start = Color(hex: "#00B386")
let end = Color.proSky._200.toOKLCH()

start.toOKLab().lerp(end.toOKLab(), t: 0.5)   // #64C9C1, a little grayer
start.toOKLCH().lerp(end, t: 0.5)             // #4BCACF
#64c9c1
OKLab halfway
#4bcacf
OKLCH halfway

To mix two tokens and keep dark mode, use blend(with:by:). .lerp(_:t:) compares every space.

Same lightness as OKLCH#

OKLab and OKLCH share their lightness, l. OKLCH writes a and b as a chroma, how far from gray, and a hue angle, so toOKLCH() turns one into the other without any change in color.

OKLab's lightness isn't CIELab L*, the lightness contrast depends on, so two colors with the same l can have different contrast on white. Why OKLCH? shows a pair.

How it behaves#

Conversions pass through extended sRGB without clipping, so a Display P3 color comes through unchanged. Inside sRGB, a runs from about −0.23 to 0.28 and b from about −0.31 to 0.2; Display P3 colors reach further. A token gives its light-mode value on iOS 17, macOS 14 and later.

From an OKLabColor you can go to toRGB(), toLAB(), toLCH(), toOKLCH() and toColor(), which gives an opaque color.

API#

swift
public extension Color {
    func toOKLab() -> OKLabColor
}

public struct OKLabColor: Hashable, Sendable {
    public let l: CGFloat       // 0 to 1
    public let a: CGFloat       // green (negative) to red (positive)
    public let b: CGFloat       // blue (negative) to yellow (positive)
    public let alpha: CGFloat   // 0 to 1

    public init(l: CGFloat, a: CGFloat, b: CGFloat, alpha: CGFloat = 1)

    public func lerp(_ other: OKLabColor, t: CGFloat) -> OKLabColor
    public func toRGB() -> RGBColor
    public func toLAB() -> LABColor
    public func toLCH() -> LCHColor
    public func toOKLCH() -> OKLCHColor
    public func toColor() -> Color
}

toOKLab() takes no parameters.

Sources#

See also

  • .toOKLCH() Read a color's lightness, chroma and hue the way the palette sees them, to compare or adjust it.
  • .lerp(_:t:) Find the color partway between two others, for your own blends, chart scales or animations.
  • .blend(with:by:) Mix two colors into one that follows dark mode, or fade a color out cleanly.
  • Why OKLCH? Every hue looks evenly matched at each stop, so you can swap families without rechecking contrast.