# .harmony(_:)

Build the harmony someone picks at runtime, from a picker or a setting, in one call.

Build a [harmony](https://colortokenskit.com/reference/glossary/index.md#harmony) chosen at runtime, such as from a picker in your theme settings, with `.harmony(_:)`. You pass a `ColorHarmony` and get its colors back in a fixed order, your own color included.

```swift
struct HarmonySwatches: View {
    let harmony: ColorHarmony

    var body: some View {
        let families = brand.harmony(harmony)
        HStack(spacing: 4) {
            ForEach(families.indices, id: \.self) { index in
                Rectangle()
                    .fill(families[index].backgroundTertiary)   // _200 in light mode, _700 in dark
                    .frame(width: 32, height: 32)
            }
        }
    }
}

VStack(alignment: .leading) {
    HarmonySwatches(harmony: .triad)                                   // 167°, 287° and 47°
    HarmonySwatches(harmony: .splitComplement(spread: .degrees(20)))   // 167°, 327° and 7°
}
```

What this draws, with `#00B386`:

| Color                           | light mode                             | dark mode                              |
| ------------------------------- | -------------------------------------- | -------------------------------------- |
| `backgroundTertiary`            | #9ce2c5                                | color(display-p3 0.1149 0.4143 0.3028) |
| `hue:286.99.backgroundTertiary` | color(display-p3 0.8192 0.8118 0.9966) | #5c539a                                |
| `hue:46.99.backgroundTertiary`  | color(display-p3 0.9816 0.7877 0.6793) | #91481f                                |
| `hue:326.99.backgroundTertiary` | #f4c5f3                                | #814781                                |
| `hue:6.99.backgroundTertiary`   | color(display-p3 0.9899 0.77 0.8087)   | #944255                                |

Pick a harmony and a color below; the Swift under the result updates as you go.

A harmony explorer marks each harmony's hues on a strip of every OKLCH hue. By default it shows the triad of `Color.proBlue._400`: #63a7fd, #ed8295, #8ab24d. [Try it on the live page](https://colortokenskit.com/api/harmony/).

## Letting people choose

`ColorHarmony` is `Hashable`, so it can be a picker's selection. Include your own spreads and offsets as separate options:

```swift
struct HarmonyPicker: View {
    @State private var harmony: ColorHarmony = .triad

    var body: some View {
        Picker("Harmony", selection: $harmony) {
            Text("Triad").tag(ColorHarmony.triad)
            Text("Square").tag(ColorHarmony.square)
            Text("Split complement").tag(ColorHarmony.splitComplement())
        }
    }
}
```

## The harmonies you can pass

Each case lists its hues as offsets around the wheel, in degrees from yours, in the order the colors come back:

| Case                        | `hueOffsets`                                  | The same as                                                                               |
| --------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `.complement`               | 0, 180                                        | [`complement`](https://colortokenskit.com/api/complement/index.md), with your color first |
| `.triad`                    | 0, 120, 240                                   | [`triad`](https://colortokenskit.com/api/triad/index.md)                                  |
| `.square`                   | 0, 90, 180, 270                               | [`square`](https://colortokenskit.com/api/square/index.md)                                |
| `.tetrad(offset:)`          | 0, offset, 180, 180 + offset                  | [`tetrad(offset:)`](https://colortokenskit.com/api/tetrad/index.md)                       |
| `.splitComplement(spread:)` | 0, 180 − spread, 180 + spread                 | [`splitComplement(spread:)`](https://colortokenskit.com/api/split-complement/index.md)    |
| `.analogous(count:spread:)` | `count` offsets `spread` apart, centered on 0 | [`analogous(count:spread:)`](https://colortokenskit.com/api/analogous/index.md)           |

Read the offsets in code when you need them, such as to label a hue wheel:

```swift
let offsets = ColorHarmony.splitComplement(spread: .degrees(20)).hueOffsets   // [0, 160, 200]
```

## How it behaves

An offset of 0 returns your color exactly as it was. Every other offset turns it with [`rotateHue(by:)`](https://colortokenskit.com/api/rotate-hue/index.md), so a palette color lands on the same stop at its new hue, with the same contrast. On a `ProTheme`, you get whole families at the stop you started from. Gray has no hue, so every harmony of a gray is gray.

`Color` and `ProTheme` read the same `ColorHarmony`, so they always agree: the complement of `brand` at `_600` is the same color whichever route you take. The one difference from the named properties is `.complement`: the property returns only the turned color, while `harmony(.complement)` returns both. [Color theory](https://colortokenskit.com/advanced/color-theory/index.md#other-hues-that-go-with-yours) covers which harmony suits which job.

## API

```swift
public extension Color {
    func harmony(_ harmony: ColorHarmony) -> [Color]
}

public extension ProTheme {
    func harmony(_ harmony: ColorHarmony) -> [ProTheme]
}

public enum ColorHarmony: Hashable {
    case complement
    case triad
    case tetrad(offset: Angle = .degrees(60))
    case square
    case splitComplement(spread: Angle = .degrees(30))
    case analogous(count: Int = 3, spread: Angle = .degrees(30))

    public var hueOffsets: [Double] { get }
}
```

| Parameter | Type           | Default | What other values do                                                        |
| --------- | -------------- | ------- | --------------------------------------------------------------------------- |
| `harmony` | `ColorHarmony` | none    | Which harmony to build. Each case's own defaults match the named functions. |

It returns one color or family per entry in `hueOffsets`, in that order.

## Sources

- ColorTokensKit source: [ColorHarmony.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Harmonies/ColorHarmony.swift), which defines every harmony's offsets in one place, and [Color+Harmonies.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Harmonies/Color+Harmonies.swift) and [ProTheme+Harmonies.swift](https://github.com/metasidd/ColorTokensKit-Swift/blob/main/Sources/ColorTokensKit/Harmonies/ProTheme+Harmonies.swift), which build the colors.

## 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.
- [.triad](https://colortokenskit.com/api/triad/index.md): Secondary and tertiary colors for tags and categories, at the same contrast as your brand.
- [.splitComplement(spread:)](https://colortokenskit.com/api/split-complement/index.md): Two accents that stand apart from your brand, softer than its opposite, at the same contrast.
- [.rotateHue(by:)](https://colortokenskit.com/api/rotate-hue/index.md): Make an accent or a whole second theme from your brand color, at the same lightness.
- [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.

---

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