# Using ColorTokensKit in AppKit

Give AppKit views the palette's tokens as NSColors that follow the Mac's light and dark appearance.

Your Mac app's AppKit views get the palette and tokens too, and they follow the Mac's light and dark appearance. Convert a token with `NSColor(_:)`, or build one from its two stops with the package's `NSColor(light:dark:)`.

```swift
import AppKit
import SwiftUI

let dates = NSTextField(labelWithString: "3 nights from October 12")
dates.font = .preferredFont(forTextStyle: .subheadline)
dates.textColor = NSColor(Color.proOrange.foregroundSecondary)   // _800 in light mode, _200 in dark
```

What this draws, with `Color.proOrange`:

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

On macOS 26.6, `NSColor(_:)` kept both stops of a token, and a [color function's](https://colortokenskit.com/api/soften/index.md) result for each appearance; we haven't tested earlier versions. It also keeps a [Display P3](https://colortokenskit.com/reference/glossary/index.md#display-p3) stop's full color, such as orange `_200` here.

## Build a token from its stops

```swift
import AppKit
import SwiftUI
import ColorTokensKit

extension ProTheme {
    /// foregroundSecondary for AppKit: _800 in light mode, _200 in dark mode.
    var nsForegroundSecondary: NSColor {
        NSColor(light: NSColor(_800.toColor()), dark: NSColor(_200.toColor()))
    }
}

let dates = NSTextField(labelWithString: "3 nights from October 12")
dates.textColor = Color.proOrange.nsForegroundSecondary
```

What this draws, with `Color.proOrange`:

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

`NSColor(light:dark:)` picks the dark stop whenever the appearance is closest to Dark Aqua, so it doesn't depend on how a macOS version bridges SwiftUI colors. [Understanding semantic tokens](https://colortokenskit.com/getting-started/semantic-tokens/index.md) lists every token's stops.

## Update layer colors when the appearance changes

```swift
import AppKit

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

    override var wantsUpdateLayer: Bool { true }

    override func updateLayer() {
        layer?.borderWidth = 1
        layer?.borderColor = outline.cgColor
    }
}

let tripCard = TripCardView(frame: NSRect(x: 0, y: 0, width: 120, height: 48))
tripCard.wantsLayer = true
```

What this draws, with `Color`:

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

A `CGColor` holds one value, so set layer colors in `updateLayer()`, which AppKit calls again when the appearance changes.

## If something goes wrong

### An AppKit color looks less vivid than in SwiftUI

The color was probably built with `NSColor(srgbRed:green:blue:alpha:)`. That tags it plain sRGB, and AppKit clips it to sRGB when it converts or draws it, so a Display P3 stop loses the chroma beyond sRGB. Build it in the `.extendedSRGB` color space instead:

```swift
import AppKit

let channels = Color.proOrange._200.toRGB()   // extended sRGB: red is past 1
let tagFill = NSColor(colorSpace: .extendedSRGB, components: [channels.r, channels.g, channels.b, channels.alpha], count: 4)
```

## API

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

A dynamic `NSColor` that gives `dark` when the drawing appearance is closest to Dark Aqua, and `light` otherwise.

## Sources

- The library's [NSColor+Dynamic.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Platform/AppKit/NSColor+Dynamic.swift), which defines `NSColor(light:dark:)`, and [NSColor+OKLCH.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Platform/AppKit/NSColor+OKLCH.swift), which builds colors in extended sRGB.
- Apple, [updateLayer()](https://developer.apple.com/documentation/appkit/nsview/updatelayer\(\)), which AppKit calls when a layer-backed view needs redrawing, including after an appearance change.

## 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.
- [Using ColorTokensKit in UIKit](https://colortokenskit.com/platforms/uikit/index.md): Give UIKit views the palette's tokens as UIColors that follow dark mode on every iOS version tested.
- [.toRGB()](https://colortokenskit.com/api/to-rgb/index.md): Read a color's red, green and blue channels, even past sRGB, to hand to other graphics code.
- [Fitting colors into Display P3](https://colortokenskit.com/under-the-hood/gamut-mapping/index.md): Stay inside Display P3 without losing contrast: chroma drops, lightness holds.

---

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