Get a finished gradient from a single color: pass a recipe, such as .tonal for depth or .fade for a glow, to proGradient(_:). A recipe turns one color into the gradient's colors, and with no recipe you get .subtle, which runs from one stop lighter at the top to the color itself:
Text("Book now")
.padding()
.foregroundStyle(.black)
.background(brand._400.proGradient()) // .subtle: _350 at the top to _400Pick a color or a token below to draw every recipe from it, in both modes. The checkerboard shows where a recipe turns transparent:
Color.proBlue._400.toColor().proGradient(.fade)
The seven presets#
| Recipe | Colors it makes from color | Good for |
|---|---|---|
.subtle (default) | color.lighten(), color | Buttons and icons |
.fade | color, color.opacity(0) | Glows and scrims |
.tonal | color.lighten(by: 2), color, color.darken(by: 2) | Cards and headers |
.analogous | color.analogous(), the hues 30° either side | Banners |
.wash | color.opacity(0.1), color.opacity(0.3) | Card backdrops |
.sheen | color.opacity(0), color.opacity(0.5), color.opacity(0) | A shine, made from white |
.edgeHighlight | color.opacity(0.15), color, color.opacity(0.15) | Borders and rims |
Every recipe works with all three shapes: proGradient, proRadialGradient and proAngularGradient.
Adding a sheen to a button#
.sheen puts a band of color across the middle, clear at both ends. Made from white and laid over a .tonal fill, it reads as a shine:
Button("Book now") {}
.buttonStyle(.plain)
.padding()
.foregroundStyle(.black)
.background(Color.white.proGradient(.sheen, from: .leading, to: .trailing))
.background(brand._400.proGradient(.tonal)) // _300 through _400 to _500Writing your own recipe#
A recipe is a function from one Color to the gradient's colors, in order. Add it as a static property, and it reads like the presets:
extension ProGradient.Recipe {
static var deepen: Self { Self { [$0, $0.darken(by: 4)] } }
}
Rectangle()
.fill(brand._400.proGradient(.deepen)) // _400 to _600
.frame(height: 64)Reading a recipe's colors#
colors(from:) gives a recipe's colors without drawing a gradient, for a chart series, a row of badges or your own gradient type:
let steps = ProGradient.Recipe.tonal.colors(from: accent) // [accent.lighten(by: 2), accent, accent.darken(by: 2)]How it behaves#
Recipes built on lighten() and darken() land palette colors on exact stops, so .tonal on _400 gives _300, _400 and _500. A color off the palette moves the same number of steps along the lightness ladder, keeping its hue and chroma.
On a family, a recipe starts from the family's own color: _450 for a built-in family, and your hex for a family made with ProTheme(hex:). To start from a stop, call it on the stop, as in brand._400.proGradient(.tonal).
The recipes that fade, .fade, .wash, .sheen and .edgeHighlight, change only opacity, so the color keeps its hue all the way to transparent. On a token they fade the token's color in each mode.
API#
public enum ProGradient {
public struct Recipe {
public init(_ makeColors: @escaping (Color) -> [Color])
public func colors(from color: Color) -> [Color]
}
}
public extension ProGradient.Recipe {
static var subtle: Self { get }
static var fade: Self { get }
static var tonal: Self { get }
static var analogous: Self { get }
static var wash: Self { get }
static var sheen: Self { get }
static var edgeHighlight: Self { get }
}| Parameter | Type | Default | What other values do |
|---|---|---|---|
makeColors (init) | (Color) -> [Color] | none | Returns the gradient's colors for a color, in order. |
color (colors(from:)) | Color | none | Any color, including tokens and system colors. |
colors(from:) returns the recipe's colors in gradient order. Pass a recipe as the first argument of proGradient, proRadialGradient or proAngularGradient on a Color or ProTheme.
Sources#
- ColorTokensKit source: ProGradient.swift and Color+ProGradient.swift.
See also
- Gradient theory Add depth with gradients that stay vivid, from a single color or a recipe, in both modes.
- Building for interaction states Give buttons and rows hover, pressed, selected and disabled colors that stay readable in both modes.
- .analogous(count:spread:) Close neighbors of your color for banners, illustrations and gradients, at matching contrast.
- How colors land on exact stops Derived colors land on exact stops, so you know their contrast before you run the app.