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.
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)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:
extension ProTheme {
var badgeBackground: Color {
Color(light: _200.toColor(), dark: _800.toColor())
}
}
Text("New")
.padding(8)
.foregroundStyle(brand.foregroundPrimary)
.background(brand.badgeBackground)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:
let page = UIColor(light: .white, dark: UIColor(Color.proGray._900.toColor()))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:
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#
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)
}| Parameter | Type | What it takes |
|---|---|---|
light | Color, LCHColor, UIColor or NSColor | The color for light mode, and for an unspecified style |
dark | the same type as light | The 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#
- The library's Color+Dynamic.swift, UIColor+Dynamic.swift and NSColor+Dynamic.swift.
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.