Make hover and pressed fills from any color with darken(by:). It moves the color down the palette by whole stops, one by default, and returns a Color that still follows dark mode:
let fill = brand.invertedBackgroundTertiary // _650 in light mode, _250 in dark
VStack(alignment: .leading) {
Text("Book now").padding(12).background(fill)
Text("Book now").padding(12).background(fill.darken()) // hovered: _700 and _300
Text("Book now").padding(12).background(fill.darken(by: 2)) // pressed: _750 and _350
}
.font(.headline)
.foregroundStyle(brand.invertedForegroundPrimary)Rest, hover and pressed are exact stops a design spec can name, and the label stays above 7:1 when pressed, in both modes and every hue. Pick a color and a number of stops below:
Color.proBlue._600.toColor().darken()With the defaults, proBlue._600 darkens to exactly proBlue._650.
Darken or strengthen?#
A pressed button should look darker in both modes. strengthen(by:) moves a color away from the page instead, so in dark mode it goes lighter, toward a light label:
let fill = brand.backgroundTertiary // _200 in light mode, _700 in dark
HStack {
Text("Book now").padding(12).background(fill.strengthen(by: 2)) // _300 and _600
Text("Book now").padding(12).background(fill.darken(by: 2)) // _300 and _800
}
.foregroundStyle(brand.foregroundPrimary)In dark mode, the strengthened fill drops the label to 4.29:1, and the darkened one keeps it at 8.72:1. Building for interaction states puts these fills in a ButtonStyle.
How it behaves#
A palette color lands on an exact stop of its family, so it has that stop's contrast in every hue. Past _1000 it stays at _1000, and black stays black. Gray moves along gray's own stops.
A color off the palette, such as a system color or your own hex, moves by the same step in lightness and keeps its hue and chroma, as how colors land on exact stops shows. The result keeps the color's opacity, and each mode darkens that mode's color.
API#
public extension Color {
func darken(by stops: Int = 1) -> Color
}| Parameter | Type | Default | What other values do |
|---|---|---|---|
stops | Int | 1 | A negative value goes lighter, so darken(by: -2) is lighten(by: 2). |
It returns a new Color that resolves in each appearance, so it follows light and dark mode like the color you started from.
Sources#
- Color+Adjustments.swift, which defines
darken(by:)aslighten(by: -stops). - ColorAdjustment.swift, which moves a color along the stops.
See also
- .lighten(by:) Add highlights and lighter steps to any color, landing on an exact palette stop.
- .desaturate(by:) Gray out disabled and finished states, and every label stays just as readable.
- .soften(by:) Make quieter text and fills from any token: lighter in light mode, darker in dark mode.
- Stops: ._50 to ._1000 Twenty fixed colors per theme, lightest to darkest, with the same contrast in every hue.