Skip to content
ColorTokensKitby Penguin Design Ventures
Contents
Function

.soften(by:)

Make quieter text and fills from any token: lighter in light mode, darker in dark mode.

Updated View as Markdown

Make quieter text, icons and fills from any token with soften(by:). It moves the color toward the page by whole stops, lighter in light mode and darker in dark mode, and returns a Color:

swift
VStack(alignment: .leading, spacing: 4) {
    Text("Lisbon")
        .font(.headline)
        .foregroundStyle(brand.foregroundPrimary)
    Text("3 nights from October 12")
        .font(.subheadline)
        .foregroundStyle(brand.foregroundSecondary)                 // _800 in light, _200 in dark
    Text("Prices checked 2 min ago")
        .font(.footnote)
        .foregroundStyle(brand.foregroundSecondary.soften(by: 2))   // _700 in light, _300 in dark
}
light mode
Lisbon3 nights from October 12Prices checked 2 min ago

Two stops toward the page, foregroundSecondary lands on the stops of foregroundTertiary, so the footnote still passes WCAG AA on the page in both modes. Pick a color and a number of stops below:

Try it / soften()
Color
light mode
Before
#4176b8proBlue._600
After
#4882caproBlue._550
dark mode
Before
#4176b8proBlue._600
After
#3a69a5proBlue._650
swift
Color.proBlue._600.toColor().soften()

With the defaults, proBlue._600 softens to proBlue._550 in light mode and proBlue._650 in dark.

Quieter fills#

A fill softens toward the page too. Here the tag you haven't picked sits one stop closer to the page than the one you have:

swift
HStack {
    Text("Beach").padding(8).background(brand.backgroundTertiary)           // _200 and _700
    Text("City").padding(8).background(brand.backgroundTertiary.soften())   // _150 and _750
}
.font(.subheadline)
.foregroundStyle(brand.foregroundPrimary)
light mode
BeachCity

Why not lighten()?#

lighten(by:) goes lighter in both modes, so lighter text is quieter on a white page and louder on a black one. soften(by:) picks the direction from the mode, so it's quieter in both.

How it behaves#

The direction comes from the appearance, not from what's behind the color. On an inverted surface, such as a dark banner in light mode, softening still goes lighter, which makes text on it louder. Use strengthen(by:) there, or the invertedForeground tokens. On watchOS, which always draws dark, softening always goes darker.

A palette color lands on an exact stop of its family, and stops at _50 or _1000. A color off the palette moves by the same step in lightness and keeps its hue. The result keeps the color's opacity.

API#

swift
public extension Color {
    func soften(by stops: Int = 1) -> Color
}
ParameterTypeDefaultWhat other values do
stopsInt1A negative value moves away from the page, so soften(by: -1) is strengthen().

It returns a new Color that resolves in each appearance: lighter in light mode and darker in dark mode.

Sources#

See also

  • Understanding semantic tokens Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.
  • Building for interaction states Give buttons and rows hover, pressed, selected and disabled colors that stay readable in both modes.
  • .darken(by:) Make hover and pressed fills from any color, an exact stop darker in both modes.
  • Managing dark mode Get dark mode without extra code: tokens switch stops for you, and your own colors can too.