# Using ColorTokensKit in UIKit

Give UIKit views the palette's tokens as UIColors that follow dark mode on every iOS version tested.

Your UIKit views get the same palette and tokens as SwiftUI, with dark mode handled. Build each token as a dynamic `UIColor` from its two stops: it switches with dark mode on every iOS version we tested, which converting the SwiftUI token doesn't always do.

```swift
import UIKit
import SwiftUI
import ColorTokensKit

extension ProTheme {
    /// foregroundSecondary for UIKit: _800 in light mode, _200 in dark mode.
    var uiForegroundSecondary: UIColor {
        UIColor(light: UIColor(_800.toColor()), dark: UIColor(_200.toColor()))
    }
}

let dates = UILabel()
dates.text = "3 nights from October 12"
dates.font = .preferredFont(forTextStyle: .subheadline)
dates.textColor = Color.proOrange.uiForegroundSecondary
```

What this draws, with `Color.proOrange`:

| Color                 | light mode | dark mode                           |
| --------------------- | ---------- | ----------------------------------- |
| `foregroundSecondary` | #6e3612    | color(display-p3 0.978 0.79 0.6716) |

`UIColor(light:dark:)` comes with the package. `UIColor` takes a SwiftUI `Color`, not a stop, so each stop goes through `toColor()`. [Understanding semantic tokens](https://colortokenskit.com/getting-started/semantic-tokens/index.md) lists every token's stops.

Palette colors travel as [extended sRGB](https://colortokenskit.com/reference/glossary/index.md#extended-srgb), so a [Display P3](https://colortokenskit.com/reference/glossary/index.md#display-p3) stop, such as orange `_200` here, keeps its full color in a `UIColor`. The same code builds for tvOS and [visionOS](https://colortokenskit.com/platforms/visionos/index.md).

## Adapt color function results

```swift
import UIKit

extension UIColor {
    /// Works out a SwiftUI color for each appearance.
    @available(iOS 17.0, tvOS 17.0, *)
    convenience init(adapting color: Color) {
        self.init { traits in
            var environment = EnvironmentValues()
            environment.colorScheme = traits.userInterfaceStyle == .dark ? .dark : .light
            let resolved = color.resolve(in: environment)
            return UIColor(
                red: CGFloat(resolved.red),
                green: CGFloat(resolved.green),
                blue: CGFloat(resolved.blue),
                alpha: CGFloat(resolved.opacity)
            )
        }
    }
}

let dates = UILabel()
dates.text = "3 nights from October 12"
dates.font = .preferredFont(forTextStyle: .subheadline)
dates.textColor = UIColor(adapting: Color.proOrange.foregroundSecondary.soften())   // _750 in light mode, _250 in dark
```

What this draws, with `Color.proOrange`:

| Color       | light mode | dark mode                             |
| ----------- | ---------- | ------------------------------------- |
| `_750/_250` | #7f4016    | color(display-p3 0.9744 0.7286 0.573) |

Color functions such as [`soften(by:)`](https://colortokenskit.com/api/soften/index.md) work out their result for each mode, and `UIColor(adapting:)` keeps that in UIKit on iOS 17 and later. On iOS 16, build the result from those stops with `UIColor(light:dark:)`.

## Update layer colors when the mode changes

```swift
import UIKit

final class TripCardView: UIView {
    // outlineSecondary: gray _200 in light mode, _800 in dark mode
    private let outline = UIColor(
        light: UIColor(Color.proGray._200.toColor()),
        dark: UIColor(Color.proGray._800.toColor())
    )

    override init(frame: CGRect) {
        super.init(frame: frame)
        layer.borderWidth = 1
        updateBorder()
        registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: TripCardView, _: UITraitCollection) in
            view.updateBorder()
        }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func updateBorder() {
        layer.borderColor = outline.resolvedColor(with: traitCollection).cgColor
    }
}

let tripCard = TripCardView(frame: CGRect(x: 0, y: 0, width: 120, height: 48))
```

What this draws, with `Color`:

| Color              | light mode | dark mode |
| ------------------ | ---------- | --------- |
| `outlineSecondary` | #dddddd    | #2c2c2c   |

A `CGColor` holds one value, so set `layer.borderColor` again when the mode changes. On iOS 16, call `updateBorder()` from `traitCollectionDidChange(_:)` instead.

## If something goes wrong

### A UIKit color stays the same in dark mode

Either a layer still holds a `CGColor` from the other mode, or the color came from `UIColor(_:)` on an adaptive color. In our tests, that kept only the light value on iOS 17 and 18.0, even on a real view, as Apple's `Color.primary` did, and followed the mode on 18.6 and 26. Build the color with `UIColor(light:dark:)` or `UIColor(adapting:)`, and set layer colors again when the mode changes.

## API

```swift
extension UIColor {
    convenience init(
        light lightModeColor: @escaping @autoclosure () -> UIColor,
        dark darkModeColor: @escaping @autoclosure () -> UIColor
    )
}
```

A dynamic `UIColor` that gives `light` in the light appearance and `dark` in the dark one; an unspecified appearance gets `light`. It's available on iOS, iPadOS, tvOS and visionOS, and not on watchOS.

## Sources

- The library's [UIColor+Dynamic.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Platform/UIKit/UIColor+Dynamic.swift), which defines `UIColor(light:dark:)` for every UIKit platform except watchOS.
- Apple, [init(dynamicProvider:)](https://developer.apple.com/documentation/uikit/uicolor/init\(dynamicprovider:\)), the dynamic `UIColor` it builds on.
- Apple, [registerForTraitChanges(\_:handler:)](https://developer.apple.com/documentation/uikit/uitraitchangeobservable-67e94/registerfortraitchanges\(_:handler:\)), for updating layer colors on iOS 17 and later.

## See also

- [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.
- [Understanding semantic tokens](https://colortokenskit.com/getting-started/semantic-tokens/index.md): Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.
- [Using ColorTokensKit in AppKit](https://colortokenskit.com/platforms/appkit/index.md): Give AppKit views the palette's tokens as NSColors that follow the Mac's light and dark appearance.
- [Using ColorTokensKit on visionOS](https://colortokenskit.com/platforms/visionos/index.md): Use the palette on Apple Vision Pro, where tokens give their dark stops and color goes on buttons.
- [High contrast modes](https://colortokenskit.com/advanced/high-contrast/index.md): Respect Increase Contrast with one rule for every family: text and borders two stops stronger.

---

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