# .desaturate(by:)

Gray out disabled and finished states, and every label stays just as readable.

Gray out disabled and finished states with `desaturate(by:)`. It takes color out at the same lightness, 20% by default and all of it at `1`, so labels keep their contrast, and returns a `Color`:

```swift
let fill = brand.invertedBackgroundTertiary   // _650 in light mode, _250 in dark

VStack(alignment: .leading) {
    Text("Book now").padding(12).background(fill)
    Text("Sold out").padding(12).background(fill.desaturate(by: 1))   // gray, as light as fill
}
.font(.headline)
.foregroundStyle(brand.invertedForegroundPrimary)
```

What this draws, with `#00B386`:

| Color                        | light mode                             | dark mode                             |
| ---------------------------- | -------------------------------------- | ------------------------------------- |
| `invertedForegroundPrimary`  | #eaf8f2                                | color(display-p3 0.0176 0.1298 0.086) |
| `invertedBackgroundTertiary` | color(display-p3 0.1368 0.4655 0.3425) | #79dab5                               |
| `#686868/#c8c8c8`            | #686868                                | #c8c8c8                               |

Sold out keeps the label's contrast in both modes, so people can still read what the button would do. Slide the amount below:

A playground applies `desaturate()` to a palette stop, a token or a hex color. By default it shows `Color.proBlue._600.toColor().desaturate()`, which gives #4d76aa in both modes. [Try it on the live page](https://colortokenskit.com/api/desaturate/).

## Partly muted

Take out half the color for things that are over but still worth showing, like a past trip next to an upcoming one:

```swift
HStack {
    Text("Lisbon").padding(12).background(brand.backgroundTertiary)
    Text("Kyoto").padding(12).background(brand.backgroundTertiary.desaturate(by: 0.5))
}
.foregroundStyle(brand.foregroundPrimary)
```

What this draws, with `#00B386`:

| Color                | light mode                            | dark mode                              |
| -------------------- | ------------------------------------- | -------------------------------------- |
| `foregroundPrimary`  | color(display-p3 0.0176 0.1298 0.086) | #eaf8f2                                |
| `backgroundTertiary` | #9ce2c5                               | color(display-p3 0.1149 0.4143 0.3028) |
| `#badbcd/#3a6554`    | #badbcd                               | #3a6554                                |

## Disabled text

Soften text until it's far from the page, then take its color out:

```swift
// Gray, 9 stops from the page
let disabled = brand.foregroundPrimary.soften(by: 10).desaturate(by: 1)

Text("Sold out").foregroundStyle(disabled)
```

What this draws:

| Color             | light mode | dark mode |
| ----------------- | ---------- | --------- |
| `#8c8c8c/#808080` | #8c8c8c    | #808080   |

The text stays at 3:1 or more in any hue. [Building for interaction states](https://colortokenskit.com/getting-started/interaction-states/index.md) shows it in a full row.

## How it behaves

At `1`, the result is a gray with the color's lightness, so it has the same contrast as the color did. It isn't a `proGray` stop, because gray's stops sit on a ladder of their own. An amount above `1` gives the same gray, and a negative amount adds color, like [`saturate(by:)`](https://colortokenskit.com/api/saturate/index.md).

Gray, white and black come back unchanged. The result keeps the color's opacity, and each mode desaturates that mode's color.

## API

```swift
public extension Color {
    func desaturate(by amount: Double = 0.2) -> Color
}
```

| Parameter | Type     | Default | What other values do                                                                |
| --------- | -------- | ------- | ----------------------------------------------------------------------------------- |
| `amount`  | `Double` | `0.2`   | The fraction of chroma to take out: `1` leaves a gray. A negative value adds color. |

It returns a new `Color` at the same lightness and hue, with less chroma, in each appearance.

## Sources

- [Color+Adjustments.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Adjustments/Color+Adjustments.swift), which defines `desaturate(by:)` as `saturate(by: -amount)`.
- [ColorAdjustment.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Adjustments/ColorAdjustment.swift), which scales chroma at the same lightness.

## See also

- [.darken(by:)](https://colortokenskit.com/api/darken/index.md): Make hover and pressed fills from any color, an exact stop darker in both modes.
- [.soften(by:)](https://colortokenskit.com/api/soften/index.md): Make quieter text and fills from any token: lighter in light mode, darker in dark mode.
- [How accessible is it?](https://colortokenskit.com/basics/how-accessible/index.md): What the palette guarantees for contrast in every hue and mode, and what you still need to check.
- [Understanding semantic tokens](https://colortokenskit.com/getting-started/semantic-tokens/index.md): Learn the 20 tokens by the job each one does, and get dark mode and passing contrast with them.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/api/desaturate/ (updated September 26, 2026).
