# Troubleshooting ColorTokensKit

Fix the problems people hit most, from colors that ignore dark mode to hex strings that vanish.

Each entry is named after what you notice, then says why and what to do. If yours isn't here, [open an issue on GitHub](https://github.com/metasidd/ColorTokensKit-Swift/issues).

## "Value of type 'ProTheme' has no member 'foregroundPrimary'"

The tokens live in `ColorTokens.swift`, a file you copy into your app, not in the package. Add it to your app target, as [Installing ColorTokensKit](https://colortokenskit.com/getting-started/installing/index.md#copy-the-semantic-tokens) shows.

## "'ProColor' is deprecated: renamed to 'ProTheme'"

`ProColor` is the old name of [`ProTheme`](https://colortokenskit.com/api/protheme/index.md). It stays as a deprecated alias, so older code still compiles, with this warning wherever it says `ProColor`:

```swift
let brand: ProColor = Color.proBlue   // 'ProColor' is deprecated: renamed to 'ProTheme'
```

Change the name and nothing else:

```swift
let brand: ProTheme = Color.proBlue
```

Xcode offers a fix-it on each warning that makes this change. For a whole project, replace `ProColor` with `ProTheme` everywhere, including your copy of `ColorTokens.swift`, which extends it. The alias is a `typealias`, so a `ProColor` and a `ProTheme` are one type: you can pass either where the other is asked for, and only the warning differs. A brand family built as `ProColor(oklch: OKLCHColor(hex:))` is now one call, [`ProTheme(hex:)`](https://colortokenskit.com/api/protheme/index.md#from-a-hex-color).

## A color doesn't change in dark mode

A stop, such as `_100`, is one fixed color in both modes. A [semantic token](https://colortokenskit.com/getting-started/semantic-tokens/index.md) moves to another stop in dark mode, so draw with tokens:

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

What this draws, with `#00B386`:

| Color                 | light mode                             | dark mode                             |
| --------------------- | -------------------------------------- | ------------------------------------- |
| `backgroundPrimary`   | #eaf8f2                                | color(display-p3 0.0176 0.1298 0.086) |
| `foregroundPrimary`   | color(display-p3 0.0176 0.1298 0.086)  | #eaf8f2                               |
| `foregroundSecondary` | color(display-p3 0.0797 0.3134 0.2262) | #9ce2c5                               |

For a pair of stops of your own, [add a token](https://colortokenskit.com/getting-started/semantic-tokens/index.md#adding-your-own-tokens).

## Converting a token gives its light-mode value

Conversions such as `toOKLCH()` and `getHexString()` read a `Color` in SwiftUI's default environment, which is light mode on iOS 17, macOS 14 and later. For dark mode, convert the stop the token uses there: `foregroundSecondary` is `_200` in dark mode, so use `brand._200.toOKLCH()`. For the current appearance, resolve the token with SwiftUI's `Color.resolve(in:)`.

## A UIColor or CGColor doesn't update when the appearance changes

In our tests on iOS 17 and 18.0, `UIColor(_:)` kept only the light value of a token, and even of Apple's `Color.primary`. Build the `UIColor` from the token's two stops instead. UIKit takes each stop as a `Color`, which is what `toColor()` is for:

```swift
let secondaryText = UIColor(   // foregroundSecondary
    light: UIColor(brand._800.toColor()),
    dark: UIColor(brand._200.toColor())
)
```

A `CGColor` holds one resolved value, so set layer colors again when the appearance changes. [UIKit](https://colortokenskit.com/platforms/uikit/index.md) and [AppKit](https://colortokenskit.com/platforms/appkit/index.md) show both fixes.

## Colors look different after updating

The palette changes between releases on purpose, so a screen can shift even where your code didn't. The rules that decide contrast hold: each stop keeps its lightness, and every token pair keeps its contrast. What can move:

- **Chroma.** Colors can get more vivid, most in the middle stops, around `_350`. Where one now looks too strong for its job, [`desaturate(by:)`](https://colortokenskit.com/api/desaturate/index.md) takes chroma back out at the same lightness.
- **Names.** Each named theme sits on a hue that's a multiple of 10°, and older names moved 5° or less to land there.
- **Themes.** Coral, amber, mustard, chartreuse, emerald, turquoise, cerulean, azure, grape, orchid and rose fill the gaps between the older names.

From a much older version the change is bigger: every hue now shares one lightness per stop, and some names moved to the hue they describe, so `proBlue`, once a cyan, is blue.

Snapshot and screenshot tests fail when colors change, so re-record their reference images once the screens look right. WCAG contrast checks keep passing. If nothing changed at all, Xcode is still resolving the old package: see [Xcode keeps an older version](https://colortokenskit.com/getting-started/installing/index.md#xcode-keeps-an-older-version).

## A saved hue number doesn't match a named theme

`primary(forHue:)` builds its ramp at exactly the hue you pass, and each named theme sits on a multiple of 10°. A hue saved from an older version, such as 251° for blue, gives a theme that looks like the named one but doesn't equal it:

```swift
let saved = ProTheme.primary(forHue: 251)   // blue's hue in an older version
let isBlue = saved == Color.proBlue          // false: proBlue is at 250°
```

Save a named theme by its name instead, and look it up with `Color.allProHues["Blue"]`.

## ColorPalettes.json or ColorRampLoader is missing

The package no longer ships a resource bundle. Every ramp is built in code, the gray one included, so there's no JSON file to load. Use a [ready-made theme](https://colortokenskit.com/api/named-themes/index.md) such as `Color.proBlue`, or build one for any hue with [`ProTheme.primary(forHue:)`](https://colortokenskit.com/api/protheme/index.md#from-a-hue).

## Colors look duller on an external monitor

The palette uses [Display P3](https://colortokenskit.com/reference/glossary/index.md#display-p3), the gamut of every iPhone since the iPhone 7, and 301 of its 720 colors outside the grays are beyond sRGB. Many external monitors show only sRGB, and the system clips those colors there, so they look less vivid in your app, the Simulator or a screenshot.

There's no sRGB fallback. Judge color on an iPhone or another Display P3 screen. [Fitting colors into Display P3](https://colortokenskit.com/under-the-hood/gamut-mapping/index.md) explains the gamut.

## Colors look less vivid than expected

We chose this. At each stop, every hue gets the same chroma, so any family can stand in for any other, and hues that could go further, such as reds and pinks, are held back to match. On an external monitor, first check [whether it shows Display P3](#colors-look-duller-on-an-external-monitor).

When one color needs more, call `saturate(by:)`. It adds chroma at the same lightness, so contrast doesn't change. `Color.proPink.foregroundTertiary.saturate(by: 1)` doubles pink's chroma in light mode, and goes as far as Display P3 allows in dark.

[What does equal lightness cost?](https://colortokenskit.com/under-the-hood/trade-offs/index.md) has the numbers for every hue.

## Yellow looks olive at darker stops

Every hue has the same lightness at each stop, so yellow at `_600` is as dark as blue at `_600`, and from there down it reads as olive. For a warm dark color, use `proGold`:

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

`saturate(by:)` can't brighten it much: at `_600`, yellow already sits near the edge of Display P3. [How the ramps are built](https://colortokenskit.com/under-the-hood/how-ramps-are-built/index.md) explains the trade-off.

## A hue number gives the wrong color

`primary(forHue:)` and the ramp generator read OKLCH hues, which don't match the hue numbers of HSL or a design tool. Apple's system blue, `#0088FF`, is 208° in HSL, and 208° in OKLCH is a cyan. Start from the color instead: `ProTheme(hex:)` builds the family at the color's own hue.

```swift
let fromHSL = ProTheme.primary(forHue: 208)   // #0088FF's HSL hue, a cyan in OKLCH
let brand = ProTheme(hex: "#0088FF")          // the color itself, at 254.09°
```

- primary(forHue: 208): #11a8c0
- ProTheme(hex:): #0088ff

[Replacing your app colors](https://colortokenskit.com/getting-started/replacing-colors/index.md) turns a brand color into a family step by step.

## A gray brand color gives a mustard family

A gray has no hue, so the hue `OKLCHColor(hex:)` reads from one is a leftover of the math, and `ProTheme.primary(forHue:)` builds a full-color family from it: for `#333333`, a dark mustard yellow. `ProTheme(hex:)` gives any color with a chroma of 0.005 or less the gray family:

```swift
let fromHue = ProTheme.primary(forHue: OKLCHColor(hex: "#333333").h)   // hue 89.88, a mustard
let brand = ProTheme(hex: "#333333")                                   // the gray family
```

- primary(forHue:): #b8940d
- ProTheme(hex:): #333333

`brand` has gray's 20 stops and tokens, and keeps #333333 as its own color.

## A hex color comes out nearly invisible

`Color(hex:)` reads 3, 6 or 8 hex digits, and any other length gives a nearly transparent color instead of an error. Eight digits are `AARRGGBB`, alpha first, not the `RRGGBBAA` order CSS uses, so move alpha to the front. [`Color(hex:)`](https://colortokenskit.com/api/color-hex/index.md) lists every hex initializer.

## A hex string doesn't match the swatch

This wiki writes palette colors as Display P3 hex, the way an iPhone shows them, and `getHexString()` returns sRGB hex. Inside sRGB, the two are one color written two ways: `Color.proBlue._600` is #4176B8 here and `2C77BD` from `getHexString()`. A stop beyond sRGB has no sRGB hex, so `getHexString()` clamps it into sRGB, and the result is duller:

- proTeal.\_400: #13b9b3
- getHexString(): #00bcb5

Name palette colors by family and stop rather than by hex.

## An oklch or lch string gives a pink color

The string parsers fall back to a default color when a string doesn't match: `#E7729B` for `oklchString` and `#DF97AC` for `lchString`.

- oklchString fallback: #e7729b
- lchString fallback: #df97ac

Write `oklch(0.68 0.14 167)` with three plain numbers: no percent signs, no alpha, and `0.68` rather than `.68`. Write `lch(65% 51 166)` with a percent sign on the lightness.

## A gradient looks gray or pale in the middle

In our measurements on iOS, SwiftUI's default gradient takes a straight path through OKLab, and between distant hues the middle of that path is gray. `proGradient()` goes around the OKLCH hue wheel instead, so the middle stays vivid:

```swift
// Lisbon to Reykjavík
let colors = [Color.proOrange.foregroundTertiary, Color.proSky.foregroundTertiary]

VStack {
    LinearGradient(colors: colors, startPoint: .leading, endPoint: .trailing)
        .frame(width: 160, height: 64)
    colors.proGradient(from: .leading, to: .trailing)
        .frame(width: 160, height: 64)
}
```

What this draws:

| Color                       | light mode                             | dark mode                             |
| --------------------------- | -------------------------------------- | ------------------------------------- |
| `orange.foregroundTertiary` | #90491a                                | color(display-p3 0.9695 0.664 0.4686) |
| `sky.foregroundTertiary`    | color(display-p3 0.0889 0.3874 0.5384) | #48c9ff                               |

It draws a linear gradient from `orange.foregroundTertiary` to `sky.foregroundTertiary`, with `.direct` and `.linear`; a linear gradient from `orange.foregroundTertiary` to `sky.foregroundTertiary`, with `.vivid` and `.smooth`.

[How gradients blend and ease](https://colortokenskit.com/under-the-hood/gradient-blends-and-easing/index.md) compares the paths.

## See also

- [Color(hex:)](https://colortokenskit.com/api/color-hex/index.md): Turn a hex value from your design file into a SwiftUI color, with or without transparency.
- [Changelog](https://colortokenskit.com/reference/changelog/index.md): See what each release changed before you update, from the first release to the latest.
- [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.
- [.lighten(by:)](https://colortokenskit.com/api/lighten/index.md): Add highlights and lighter steps to any color, landing on an exact palette stop.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/reference/troubleshooting/ (updated September 26, 2026).
