Skip to content
ColorTokensKitby Penguin Design Ventures
Contents
Function

Color(light:dark:)

Make any color switch between light and dark mode, the way every token does.

Updated View as Markdown

Color(light:dark:) makes one color that switches with light and dark mode, the way every token in ColorTokens.swift does. It takes a color for each mode, as SwiftUI Colors or LCHColors, and returns a SwiftUI Color you can use anywhere.

swift
extension Color {
    static var page: Color {
        Color(light: .white, dark: Color.proGray._900.toColor())   // softer than pure black
    }
}

Text("Lisbon")
    .padding()
    .foregroundStyle(Color.foregroundPrimary)
    .background(Color.page)
light mode
Lisbon

Adding a token to every theme#

The built-in tokens are two stops each, joined by Color(light:dark:). Add your own the same way, in an extension on ProTheme, and every theme gets it, your brand's included:

swift
extension ProTheme {
    var badgeBackground: Color {
        Color(light: _200.toColor(), dark: _800.toColor())
    }
}

Text("New")
    .padding(8)
    .foregroundStyle(brand.foregroundPrimary)
    .background(brand.badgeBackground)
light mode
New

Defining a token is where toColor() belongs: it turns each stop into the Color this initializer takes. Views then draw with the token.

UIKit and AppKit#

UIColor and NSColor get the same initializer, for code that can't use a SwiftUI Color:

swift
let page = UIColor(light: .white, dark: UIColor(Color.proGray._900.toColor()))
swift
let page = NSColor(light: .white, dark: NSColor(Color.proGray._900.toColor()))

ColorTokensKit in UIKit and in AppKit show tokens built this way.

From LCH values#

A second version takes LCHColors, for colors you already have as CIELab lightness, chroma and hue:

swift
let tint = Color(light: LCHColor(l: 45, c: 40, h: 270), dark: LCHColor(l: 80, c: 30, h: 270))

How it behaves#

Underneath, it's a system color that picks a side each time it's drawn: a UIColor that reads the trait collection on iOS, tvOS and visionOS, and an NSColor that reads the appearance on macOS. So a view switches with the system setting, or with .preferredColorScheme, without being rebuilt. When the style is unspecified, UIKit gets the light color.

watchOS always draws in the dark appearance, so there it's simply the dark color.

It only knows light and dark. For a color that also changes with Increase Contrast, see high contrast modes.

You rarely need it for states. darken(by:), soften(by:) and the other color functions work out their result in each mode, so brand.backgroundTertiary.darken() already follows dark mode.

API#

swift
public extension Color {
    init(light: @escaping @autoclosure () -> Color, dark: @escaping @autoclosure () -> Color)
    init(light: @escaping @autoclosure () -> LCHColor, dark: @escaping @autoclosure () -> LCHColor)
}

public extension UIColor {   // not on watchOS
    convenience init(light: @escaping @autoclosure () -> UIColor, dark: @escaping @autoclosure () -> UIColor)
}

public extension NSColor {
    convenience init(light: @escaping @autoclosure () -> NSColor, dark: @escaping @autoclosure () -> NSColor)
}
ParameterTypeWhat it takes
lightColor, LCHColor, UIColor or NSColorThe color for light mode, and for an unspecified style
darkthe same type as lightThe color for dark mode, and the only color on watchOS

Each returns a color of its own type that shows light or dark for the current appearance.

Sources#

See also

  • Managing dark mode Get dark mode without extra code: tokens switch stops for you, and your own colors can too.
  • Stops: ._50 to ._1000 Twenty fixed colors per theme, lightest to darkest, with the same contrast in every hue.
  • Understanding semantic tokens Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.
  • High contrast modes Respect Increase Contrast with one rule for every family: text and borders two stops stronger.
  • Using ColorTokensKit in UIKit Give UIKit views the palette's tokens as UIColors that follow dark mode on every iOS version tested.