# .rotateHue(by:)

Make an accent or a whole second theme from your brand color, at the same lightness.

Make an accent, or a whole second theme, from your brand color with `rotateHue(by:)`. It turns the hue around the OKLCH color wheel and keeps the lightness, so contrast doesn't change. On a `ProTheme` it returns a theme at the new hue, with every token; on a `Color` it returns one color:

```swift
let accent = brand.rotateHue(by: .degrees(-120))   // your brand's theme, turned back 120°

HStack {
    Text("Book now")
        .padding(12)
        .foregroundStyle(brand.foregroundPrimary)
        .background(brand.backgroundTertiary)
    Text("€480")
        .padding(12)
        .foregroundStyle(accent.foregroundPrimary)
        .background(accent.backgroundTertiary)
}
```

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) |
| `hue:46.99.foregroundPrimary`  | #311405                                | color(display-p3 0.9945 0.9523 0.9287) |
| `hue:46.99.backgroundTertiary` | color(display-p3 0.9816 0.7877 0.6793) | #91481f                                |

If your brand color changes, the accent follows at the same angle. Turn the hue below: a palette color lands on the same stop of the new hue, so its contrast stays the same:

A playground applies `rotateHue()` to a palette stop, a token or a hex color. By default it shows `Color.proBlue._600.toColor().rotateHue(by: .degrees(30))`, which gives #696bbc in both modes. [Try it on the live page](https://colortokenskit.com/api/rotate-hue/).

## One color at a time

On a token, `rotateHue(by:)` turns just that color. Here a deal badge uses the hue opposite your brand:

```swift
let text = brand.foregroundPrimary.rotateHue(by: .degrees(180))    // _1000 in light, _50 in dark
let fill = brand.backgroundTertiary.rotateHue(by: .degrees(180))   // _200 in light, _700 in dark

Text("Deal").padding(8).foregroundStyle(text).background(fill)
```

What this draws:

| Color                           | light mode                            | dark mode |
| ------------------------------- | ------------------------------------- | --------- |
| `hue:346.99.foregroundPrimary`  | #2f1222                               | #fff1f8   |
| `hue:346.99.backgroundTertiary` | color(display-p3 0.9671 0.7712 0.879) | #8d436d   |

When you need several colors from one hue, turn the theme once instead, as in the first example. For the opposite hue, [`complement`](https://colortokenskit.com/api/complement/index.md) does the same turn.

## Angles are OKLCH hues

The angle turns the hue on the [OKLCH](https://colortokenskit.com/reference/glossary/index.md#oklab-and-oklch) wheel, where blue sits near 250° and red near 20°. An angle from a design tool's HSB wheel won't land on the same color. [`triad`](https://colortokenskit.com/api/triad/index.md) and [`harmony(_:)`](https://colortokenskit.com/api/harmony/index.md) turn one color into evenly spaced sets.

## How it behaves

A palette color lands on the same stop of the new hue's ramp. On a `ProTheme`, every stop and token matches [`ProTheme.primary(forHue:)`](https://colortokenskit.com/api/protheme/index.md#from-a-hue) at the new hue. A color off the palette keeps its lightness and chroma, as much as Display P3 can show at the new hue.

Gray, white and black have no hue, so they come back unchanged, and so does `Color.proGray`. Angles past 360° wrap around, and a negative angle turns the other way. The result keeps the color's opacity.

## API

```swift
public extension Color {
    func rotateHue(by angle: Angle) -> Color
}

public extension ProTheme {
    func rotateHue(by angle: Angle) -> ProTheme
}
```

| Parameter | Type    | Default | What other values do                                                |
| --------- | ------- | ------- | ------------------------------------------------------------------- |
| `angle`   | `Angle` | none    | A negative angle turns the other way. Angles past 360° wrap around. |

On a `Color`, it returns a new `Color` that follows light and dark mode. On a `ProTheme`, it returns a `ProTheme` at the new hue.

## Sources

- [Color+Adjustments.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Adjustments/Color+Adjustments.swift), which defines `rotateHue(by:)` on `Color`.
- [ProTheme+Harmonies.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Harmonies/ProTheme+Harmonies.swift), which defines it on `ProTheme`.
- [ColorAdjustment.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Adjustments/ColorAdjustment.swift), which lands palette colors on the new hue's stop.

## See also

- [Color theory](https://colortokenskit.com/advanced/color-theory/index.md): Pair stops that always read, and pick secondary hues that match your brand, in both modes.
- [Using tokens in charts](https://colortokenskit.com/advanced/charts/index.md): Give every chart series equal contrast, plus a second cue for readers who can't tell hues apart.
- [Setting up themes](https://colortokenskit.com/advanced/themes/index.md): Recolor a view, a screen or your whole app from one value, with the same contrast in every hue.
- [ProTheme](https://colortokenskit.com/api/protheme/index.md): Make a theme from a hex, a hue or OKLCH numbers, and every stop and token keeps its contrast.

---

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