# Why use ColorTokensKit over native colors?

What you gain over SwiftUI's system colors, asset catalogs and hex values, and what it costs.

ColorTokensKit gives your SwiftUI app the color system a design team would build for it. To see what that adds, start with what SwiftUI gives you on its own.

## What SwiftUI gives you

```swift
// 12 system colors, each with a light and a dark version
let accents: [Color] = [.red, .orange, .yellow, .green, .mint, .teal, .cyan, .blue, .indigo, .purple, .pink, .brown]
let brand = Color("Brand")   // your own colors, from an asset catalog

VStack(alignment: .leading) {
    Text("Lisbon")
        .foregroundStyle(.primary)                // black in light mode, white in dark
    Text("3 nights from October 12")
        .font(.subheadline)
        .foregroundStyle(.secondary)              // a dimmer primary; .tertiary and .quaternary go further
    Text("Beach")
        .padding(4)
        .background(Color.orange.quaternary)      // any color has .secondary to .quinary levels
    RoundedRectangle(cornerRadius: 8)
        .fill(Color.orange.gradient)              // and a gentle gradient
        .frame(height: 24)
}
.padding()
.background(.background)                          // white in light mode, black in dark
.tint(.orange)                                    // the accent for buttons and controls
```

- Color.red: #ff383c
- Color.orange: #ff8d28
- Color.yellow: #ffcc00
- Color.green: #34c759
- Color.mint: #00c8b3
- Color.teal: #00c3d0
- Color.cyan: #00c0e8
- Color.blue: #0088ff
- Color.indigo: #6155f5
- Color.purple: #cb30e0
- Color.pink: #ff2d55
- Color.brown: #ac7f5e

Each color also has four lighter levels. On iPhone, `.secondary` is the color at 50% opacity, `.tertiary` at 25%, and `.quaternary` at about 18%; in our tests `.quinary` drew the same as `.quaternary`. Here they are on white:

- Color.red: #ff383c
- Color.red.secondary: #ff9c9e
- Color.red.tertiary: #ffcdce
- Color.red.quaternary: #ffdbdc

* Color.blue: #0088ff
* Color.blue.secondary: #80c3ff
* Color.blue.tertiary: #bfe1ff
* Color.blue.quaternary: #d1eaff

That's a good start: text that follows dark mode, a background that does too, a dozen accents, and lighter levels of each.

## Where native colors fall short

- **Uneven.** Each system color is tuned on its own, so as text on white they run from 1.51:1 to 5.09:1. Only indigo reaches the 4.5:1 that WCAG AA asks for body text.
- **Levels, not shades.** `.secondary` to `.quaternary` are the same color at lower opacity, so they blend with whatever sits behind them: pink on white, a dim red on black. None of them is a deeper red for text on white, and each level measures differently on every background.
- **No palette to grow into.** Your brand color, or a 13th hue, means building its whole range of shades by hand.
- **Verbose.** Every custom color is a hex value or an asset catalog entry, with a light and a dark variant, for each shade you use.
- **Dark mode by hand.** System colors switch on their own, but every color you add needs a dark value you pick and check.
- **Hard to scale.** Each new theme, pressed state or disabled state is another set of colors to pick and check for contrast.

## Benefit 1: The same contrast in every hue

- Color.red, 3.57:1: #ff383c
- Color.green, 2.22:1: #34c759
- Color.blue, 3.52:1: #0088ff

* proRed.\_600, 4.68:1: #ad5a5a
* proGreen.\_600, 4.68:1: #44824b
* proBlue.\_600, 4.68:1: #4176b8

The top row is SwiftUI's system colors as text on white, and the bottom row is ours at `_600`. Our 36 hues share one lightness at each stop, so a stop measures the same in every one: `_600` passes AA as text on white in all of them, including a hue you make from your brand color. [How did we choose and build these colors?](https://colortokenskit.com/basics/how-the-colors-were-built/index.md) explains how.

## Benefit 2: Dark mode in every token

```swift
VStack(alignment: .leading, spacing: 4) {
    Text("Lisbon")
        .font(.headline)
        .foregroundStyle(Color.proOrange.foregroundPrimary)     // _1000 in light mode, _50 in dark
    Text("3 nights from October 12")
        .font(.subheadline)
        .foregroundStyle(Color.proOrange.foregroundSecondary)   // _800 in light mode, _200 in dark
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.proOrange.backgroundPrimary)                  // _50 in light mode, _1000 in dark
```

What this draws, with `Color.proOrange`:

| Color                 | light mode                             | dark mode                              |
| --------------------- | -------------------------------------- | -------------------------------------- |
| `backgroundPrimary`   | color(display-p3 0.9936 0.9528 0.9271) | #301404                                |
| `foregroundPrimary`   | #301404                                | color(display-p3 0.9936 0.9528 0.9271) |
| `foregroundSecondary` | #6e3612                                | color(display-p3 0.978 0.79 0.6716)    |

Each token picks one stop for light mode and one for dark, chosen so text passes on its background in both. You never pick a dark value or check it by hand. [What are tokens?](https://colortokenskit.com/basics/what-are-tokens/index.md) shows how a token picks its stops.

## Benefit 3: A new theme in one line

```swift
struct Tag: View {
    let title: String
    let theme: ProTheme

    var body: some View {
        Text(title)
            .font(.subheadline)
            .padding(.vertical, 4)
            .padding(.horizontal, 10)
            .foregroundStyle(theme.foregroundPrimary)
            .background(theme.backgroundTertiary)
    }
}

HStack {
    Tag(title: "Beach", theme: Color.proSky)
    Tag(title: "City", theme: Color.proPink)
    Tag(title: "Food", theme: Color.proOrange)
}
```

What this draws:

| Color                       | light mode                             | dark mode                              |
| --------------------------- | -------------------------------------- | -------------------------------------- |
| `sky.foregroundPrimary`     | color(display-p3 0.0119 0.1193 0.1786) | #eaf7fe                                |
| `sky.backgroundTertiary`    | #9addfd                                | color(display-p3 0.0889 0.3874 0.5384) |
| `pink.foregroundPrimary`    | #31111c                                | color(display-p3 0.9953 0.9483 0.9611) |
| `pink.backgroundTertiary`   | color(display-p3 0.9839 0.7697 0.8336) | #92425e                                |
| `orange.foregroundPrimary`  | #301404                                | color(display-p3 0.9936 0.9528 0.9271) |
| `orange.backgroundTertiary` | color(display-p3 0.978 0.79 0.6716)    | #90491a                                |

Every family has the same tokens at the same contrast, so a view that takes a family can wear any of them. The label measures 11.48:1 in light mode and 6.10:1 in dark, in all three themes and every other hue. [Setting up themes](https://colortokenskit.com/advanced/themes/index.md) shows how far this goes.

## Benefit 4: Pressed and disabled from the color you have

```swift
let fill = theme.invertedBackgroundTertiary   // theme is any family: _650 in light mode, _250 in dark

HStack {
    Text("Book now").padding(12).background(fill)
    Text("Book now").padding(12).background(fill.darken(by: 2))       // pressed: _750 in light mode, _350 in dark
    Text("Sold out").padding(12).background(fill.desaturate(by: 1))   // disabled: gray at the same lightness
}
.foregroundStyle(theme.invertedForegroundPrimary)
```

What this draws:

| Color                        | Color.proOrange light mode             | Color.proOrange dark mode              | Color.proPink light mode               | Color.proPink dark mode                | Color.proSky light mode                | Color.proSky dark mode                 |
| ---------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- |
| `invertedForegroundPrimary`  | color(display-p3 0.9936 0.9528 0.9271) | #301404                                | color(display-p3 0.9953 0.9483 0.9611) | #31111c                                | #eaf7fe                                | color(display-p3 0.0119 0.1193 0.1786) |
| `invertedBackgroundTertiary` | #a15320                                | color(display-p3 0.9744 0.7286 0.573)  | #a34b6a                                | color(display-p3 0.9812 0.702 0.7888)  | color(display-p3 0.1087 0.4359 0.6024) | #77d3fe                                |
| `_750/_350`                  | #7f4016                                | color(display-p3 0.9658 0.5958 0.3524) | #813952                                | color(display-p3 0.9731 0.5553 0.6988) | color(display-p3 0.0748 0.3398 0.4743) | color(display-p3 0.2812 0.7355 0.9808) |
| `#686868/#C8C8C8`            | #686868                                | #c8c8c8                                | #686868                                | #c8c8c8                                | #686868                                | #c8c8c8                                |

With native colors, each state is one more color to pick and check. Here, `darken(by: 2)` moves the fill two stops, so the pressed color is still a palette color, and `desaturate(by: 1)` keeps the lightness, so "Sold out" keeps the button's contrast: 5.11:1 in light mode and 10.20:1 in dark. [`darken(by:)`](https://colortokenskit.com/api/darken/index.md) and [`desaturate(by:)`](https://colortokenskit.com/api/desaturate/index.md) explain both.

## What it costs

- Color.red: #ff383c
- proRed.\_350: #fd8f8e

* **Quieter colors.** Keeping every hue evenly matched costs vividness: `proRed._350`, our most vivid red, has 70% of the [chroma](https://colortokenskit.com/reference/glossary/index.md#chroma) of `Color.red`.
* **Olive yellows.** Yellow reads as olive from `_600` down, where it's as dark as blue.
* **Your brand sits between stops.** Your brand's exact hex usually isn't one of its family's stops, though `toColor()` still gives you the exact hex.
* **Increase Contrast is up to you.** Tokens don't follow it on their own; [high contrast modes](https://colortokenskit.com/advanced/high-contrast/index.md) shows how.
* **Display P3 only.** Many stops are beyond sRGB, with no fallback, so an sRGB-only screen, such as many external monitors, clips them.
* **One more dependency.** The package has none of its own.

[What does equal lightness cost?](https://colortokenskit.com/under-the-hood/trade-offs/index.md) has the numbers. You don't have to switch all at once: [replacing your app colors](https://colortokenskit.com/getting-started/replacing-colors/index.md) moves one screen at a time.

## Sources

- Apple, [Human Interface Guidelines: Color](https://developer.apple.com/design/human-interface-guidelines/color), for the system colors, in their iOS 26 light-mode values.
- W3C, [WCAG 2.2: contrast minimum](https://www.w3.org/TR/WCAG22/#contrast-minimum).
- The library's [ColorTokens.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Tests/ColorTokensKitTests/Marketing/Setup/ColorTokens.swift), which defines the tokens.

## See also

- [What does the library offer?](https://colortokenskit.com/basics/color-library/index.md): 36 color themes, ready-to-use tokens with dark mode, a theme for your brand, and state functions.
- [Using your first tokens](https://colortokenskit.com/getting-started/first-tokens/index.md): Color text, buttons, cards and lists with tokens, then theme a movie card in one line.
- [How accessible is it?](https://colortokenskit.com/basics/how-accessible/index.md): What the palette guarantees for contrast in every hue and mode, and what you still need to check.
- [Why OKLCH?](https://colortokenskit.com/under-the-hood/why-oklch/index.md): Every hue looks evenly matched at each stop, so you can swap families without rechecking contrast.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/basics/why-colortokenskit/ (updated September 26, 2026).
