# What does equal lightness cost?

What equal contrast costs, from quieter reds to olive yellows, and what you can do about each.

The live page plots every hue from 0° to 360° at one stop's lightness: the most chroma Display P3 allows there, the one chroma the palette uses at that stop, and the hues that can't reach it. You drag across the 20 stops to move it, and can draw it flat, as a hue wheel, or in 3D as the whole Display P3 gamut with the palette's 720 colors inside. It starts at `_400`, where the palette's chroma is 0.161. [Try it on the live page](https://colortokenskit.com/under-the-hood/trade-offs/).

The faded color above the line is what the palette leaves out, so that every hue at a stop is equally vivid and has the same contrast. At `_400`, plum, magenta and the greens give up the most. Pick `_600`, the first stop that passes WCAG AA for text on white, and the purples, pinks and reds use less than half the chroma Display P3 allows them.

## Why every hue shares one chroma

- Rejected yellow \_50: #fff6c0
- Rejected blue \_50: #eef6ff
- Rejected indigo \_1000: #0e006b
- Rejected green \_1000: #00220a

These stops come from a palette we tried and rejected, which gave each hue the most chroma sRGB allows at each stop's L\*. Contrast stayed equal, but vividness didn't. At `_50`, yellow got 3.5 times the median chroma, so it looked far more colorful than the rest. At `_1000`, indigo, iris and violet got more than twice the median, and in blues extra chroma also reads as brighter. That's the Helmholtz–Kohlrausch effect, strong in blues and negligible in yellows.

So each stop keeps one chroma for every hue, and the palette gets more vivid only through Display P3's wider range.

## Quieter than other palettes

- proRed.\_400: #ef8383
- Tailwind red-500: #fb2c36

Both palettes go past sRGB, but Tailwind lets each hue pick its own lightness and chroma, so its colors are more vivid. Ours hold one lightness and one chroma per stop, so contrast is the same in every hue. When one color should stand out, [`saturate(by: 1)`](https://colortokenskit.com/api/saturate/index.md) gives it nearly the most chroma Display P3 allows, at the same contrast:

```swift
Color.proRed._600.toColor().saturate(by: 1)   // twice the chroma, still 4.68:1 on white
```

The result leaves the palette, which is fine for an accent.

## Less color on sRGB screens

The palette is Display P3, with no sRGB fallback, like Tailwind's. On a screen that only shows sRGB, such as many external monitors, the system clips the 301 stops beyond sRGB, so they look less vivid there and their contrast can shift a little. [sRGB-only screens](https://colortokenskit.com/under-the-hood/gamut-mapping/index.md#srgb-only-screens) shows how much.

## Yellow turns olive at the darker stops

- proYellow.\_600: #837509
- proGold.\_600: #956d09
- proYellow.\_200: #ded59e

Readable yellow text on white needs `_600`, as dark as blue `_600`, and a dark yellow reads as olive. For a darker, warmer yellow, use `proGold`. For a yellow that reads as yellow, use a light stop such as `_200`.

## Dark stops keep a little hue

- proBlue.\_1000: #0b1c32
- proGray.\_1000: #000001

A family's `backgroundPrimary` uses `_1000` in dark mode, which keeps a little chroma, so a blue-themed screen is navy, not black. When you want black, use the app-wide `Color.backgroundPrimary` from `ColorTokens.swift`.

## Telling neighboring hues apart

- proPink.\_200: #fbc4d5
- proRuby.\_200: #fdc4cb
- proRed.\_200: #fec5c3
- proTomato.\_200: #fdc6ba

Every hue sits 10° from its neighbors, so the light stops of pink, ruby, red and tomato are hard to tell apart. At any one stop, two families also differ only in hue, which some people with a color vision deficiency can't distinguish. When colors carry meaning, pick families further apart on the wheel, as [harmonies](https://colortokenskit.com/advanced/color-theory/index.md) do, and add a label, an icon or a difference in lightness, as WCAG asks. [Using tokens in charts](https://colortokenskit.com/advanced/charts/index.md) covers this for data.

## Other contrast models

WCAG 2 contrast is identical across hues at each stop, because it depends only on luminance. APCA, a newer model proposed for WCAG 3, varies a little by hue: it rates `_700` text on `_100` between 69.5 and 70.3.

## What it isn't designed for

ColorTokensKit is built for app interfaces in SwiftUI. It needs extra work, or isn't a good fit, when:

- You must match an exact brand hex. `ProTheme(hex: "#00B386")` keeps #00B386 as the family's own color, but its stops follow the palette's lightness and chroma, and #00B386 isn't one of them. [Replacing your app colors](https://colortokenskit.com/getting-started/replacing-colors/index.md) shows how to build that family.
- You need HDR color, or a fallback designed for sRGB screens. The palette is Display P3 only.
- You're choosing colors for print, photos or illustrations. The palette is tuned for readable screens.
- Your app is built mostly in UIKit or AppKit. The functions return SwiftUI `Color`, and [UIKit](https://colortokenskit.com/platforms/uikit/index.md) and [AppKit](https://colortokenskit.com/platforms/appkit/index.md) show how to convert colors that adapt to dark mode.

## Sources

- W3C, [WCAG 2.2 success criterion 1.4.1, use of color](https://www.w3.org/TR/WCAG22/#use-of-color), and [1.4.3, contrast minimum](https://www.w3.org/TR/WCAG22/#contrast-minimum).
- Björn Ottosson, [A perceptual color space for image processing](https://bottosson.github.io/posts/oklab/), which defines OKLab and OKLCH.
- APCA, the [Accessible Perceptual Contrast Algorithm](https://github.com/Myndex/SAPC-APCA).
- High, Green and Nussbaum, [on the Helmholtz–Kohlrausch effect](https://doi.org/10.1002/col.22839) (2023), which is strong in blues and negligible in yellows.
- The library's [UniformRamp.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Services/Ramps/UniformRamp.swift), which defines the lightness and chroma of each stop.
- Tailwind CSS, [theme.css](https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css) (version 4.3.3), and the [Tailwind CSS v4.0 announcement](https://tailwindcss.com/blog/tailwindcss-v4) (2025), on its wider-gamut palette.

## See also

- [Color theory](https://colortokenskit.com/advanced/color-theory/index.md): Pair stops that always read, and pick secondary hues that match your brand, in both modes.
- [How the ramps are built](https://colortokenskit.com/under-the-hood/how-ramps-are-built/index.md): The lightness and chroma behind every stop, so any hue you pick gives the same contrast.
- [Managing dark mode](https://colortokenskit.com/getting-started/dark-mode/index.md): Get dark mode without extra code: tokens switch stops for you, and your own colors can too.
- [.contrastRatio(to:method:)](https://colortokenskit.com/api/contrast-ratio/index.md): Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.
- [How do other color systems choose their colors?](https://colortokenskit.com/under-the-hood/other-color-systems/index.md): Compare how Material, Radix, Tailwind and others pick colors, and what each choice costs you.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/under-the-hood/trade-offs/ (updated September 26, 2026).
