# color palettes

> `Palette.From("#seed")` turns one brand color into a full ramp of shades. A bare reference (`Primary`) is the seed itself; `Primary.Hover` and `Primary[600]` reach the named and numbered steps of its ramp.

<!-- id: ui-palette · area: ui · stability: stable · html: https://osysharp.com/reference/ui/palette/ -->

## Summary        {#summary}
A **palette** turns a single brand color into a full **ramp** — a scale of shades from very light to very dark, plus
named steps for common jobs like a hover state. You give one seed color; the palette generates the rest, evenly, so a
button has a darker shade to hover to and a light tint to sit on without you hand-picking each one.

```osy title="one seed, a whole ramp" test app=ui-palette
theme Default {
  Colors {
    Primary = Palette.From("#0077B6");   // one seed → a whole ramp
  }
}
```

## Signature      {#signature}
```osy syntax
Primary = Palette.From("#0077B6");   // declare a palette from a seed color
Bg = Primary.Hover;                  // a named step
Bg = Primary[600];                   // a numbered step (50…950)
```

## Description    {#description}

### One seed, a full ramp   {#from}
`Palette.From("#seed")` declares a **palette token**. From the one seed color it generates an even ramp of shades —
computed in a perceptual color space, so the steps read as evenly spaced and the hue stays true from the lightest tint
to the darkest shade (a plain "lighten/darken" drifts and muddies; this does not).

```osy title="two ramps" test app=ui-palette-two
theme Brand {
  Colors {
    Primary = Palette.From("#0077B6");
    Accent  = Palette.From("#C0392B");
  }
}
```

The seed must be a hex color literal (`"#0077B6"` or the short `"#07B"`). Anything else — `Palette.From(Primary)`,
`Palette.From()` — is a compile error, so a mistyped palette can't slip through as an empty one.

### Getting the exact seed color back — the bare name   {#base}
Referencing the palette by its **bare name** gives you the **seed exactly as you typed it** — your brand color, not a
generated approximation of it:

```osy title="the ramp's base step" test app=ui-palette
[Composable] component Fill() {
  variants { base { Bg = Colors.Primary; } }   // Bg is exactly #0077B6
  render { Box(); }
}
```

Like any [theme tokens](https://osysharp.com/reference/ui/theming/) token reference, a bare palette name is a **living reference**, so restyling the seed carries
through everywhere it's used with no rebuild.

### Named steps   {#semantic-steps}
A palette exposes a small set of **named steps** for the jobs a color actually does in an interface. Reach them with a
member access:

```osy title="named steps" test app=ui-palette
[Composable] component Swatch() {
  variants {
    base {
      Bg = Colors.Primary; Color = Primary.OnColor;   // fill + a legible foreground on it
      Hover { Bg = Primary.Hover; }            // a step darker on hover
    }
  }
  render { Box(); }
}
```

| Step | What it's for |
|---|---|
| `Subtle` | a faint tint — a hover background, a selected row |
| `Muted` | a soft fill — a chip, a well |
| `Default` | the palette's mid shade |
| `Hover` | one step stronger than the base — a hover/emphasis fill |
| `Active` | stronger still — a pressed state |
| `Strong` | the darkest useful shade — high-emphasis text or borders |
| `OnColor` | the **foreground** color to place *on* the palette — chosen automatically (black or white) for legible contrast against the base |
| `Text` | the hue **set as text** on the page — the color itself when it reads on your surface (4.5:1), else the nearest step of its ramp that does |

`OnColor` is the one that isn't a shade of the hue: it's whichever of black or white reads clearly on your brand
color, worked out for you — so `Color = Primary.OnColor` is legible whether your brand is a deep navy or a pale amber.

`Text` is the brand color as **ink**: a link, a ghost button, the selected tab. A brand color is a fill first, and as
text it reads only when it is dark enough on a light page (light enough on a dark one) — a yellow brand's links are
about 1.5:1 on white. `Text` is measured against your theme's own `Surface`, so it is the color itself whenever that
already reads, and otherwise the closest step of the same hue that does. The kit's own controls write their text in it.

```osy title="text in the brand color, legible whatever the brand is" test app=ui-palette-text
[Composable] component Highlight(string words) {
  variants {
    base { Color = Primary.Text; FontWeight = FontWeight.Semibold; }
  }
  render { Text(words); }
}
```
Naming a step that doesn't exist (`Primary.Hund`) is a compile error listing the ones that do.

### Numbered steps   {#numeric-steps}
Under the named steps is a **numeric ramp** — `50` (lightest) through `950` (darkest), in the familiar `50, 100, 200 …
900, 950` scale. Use it when you want an exact step the names don't single out:

```osy title="numbered steps" test app=ui-palette-steps
theme Brand {
  Colors {
    Primary = Palette.From("#0077B6");
    Line    = Primary[200];    // a light hairline from the same ramp
    Ink     = Primary[900];    // near-black, still on-brand
  }
}
```

The named steps are aliases over this ramp (`Hover` is the `700` step, `Default` the `500`, and so on), so
`Primary.Hover` and `Primary[700]` are the same color — pick whichever reads better where you use it. A number outside
the `50…950` scale (`Primary[550]`) is a compile error listing the valid steps.

### Where palettes work   {#where}
A palette step is an ordinary style value, so it works anywhere a color does — a **theme token**:

```osy title="one token derived from another's step" test app=ui-palette-derive
theme Brand {
  Colors {
    Primary = Palette.From("#0077B6");
    Focus   = Primary.Active;    // derive one token from another palette's step
  }
}
```

…and a **`variants` style-prop** (as in the button above). In both, a palette step stays a **living reference** into the
ramp — change the seed and every shade derived from it moves with it, with no rebuild.

### A series led by your accent — `Palette.Series`   {#series}
A chart's series colours have two jobs: belong to the app (its accent leads, so a chart looks like part of the page)
and stay tellable apart. `Palette.Series(slot, lead, hues…)` does both: slot 1 is the lead itself, and slot `n` is the
`n−1`th of the hues that is **not too close to the lead** to tell apart from it — in light OR dark mode, so a series
keeps one hue in both. The choice is made when the app compiles, against the lead the app actually wears, so an
orange accent never sits beside an orange series:

```osy title="a series that follows the theme's accent" test app=ui-palette-series
theme Brand {
  Colors {
    Primary = Modes.Of(light: Palette.From("#C2410C"), dark: Palette.From("#FF7A3D"));
    Series1 = Colors.Primary;
    // orange is too close to this accent, so slot 2 is the blue and slot 3 the aqua
    Series2 = Palette.Series(2, Colors.Primary, "#eb6834", "#2a78d6", "#1baf7a");
    Series3 = Palette.Series(3, Colors.Primary, "#eb6834", "#2a78d6", "#1baf7a");
  }
}
```

The hues keep the order you wrote — order them for colour-vision-deficiency separation, as the chart kit does
([The chart kit — line, column, bar, scatter and candle, with no JavaScript](https://osysharp.com/reference/ui/chart-kit/)) — and a slot past the last hue left takes that last one (a ninth series is a signal to group, not a
new hue). A lead that is not a readable colour (a ramp step, an `rgba(…)`) drops nothing. "Too close" is the ΔE2000
distance at which two colours stop reading as different signals (20). Each slot compiles to an ordinary
`Palette.From`, so it has a ramp like any palette token, and `osy kit tokens --app` shows the hue the app got.

The chart kit's own `Series1…Series8` are written this way, which is why a chart follows whatever theme or look the app
wears; an app that wants a different series shadows the token with its own value.

## See also   {#see-also}
- [theme tokens](https://osysharp.com/reference/ui/theming/) — the `theme` block, token groups, references, and dark mode.
- [component](https://osysharp.com/reference/ui/component/) — declaring a component and styling it with `variants`.
- [The chart kit — line, column, bar, scatter and candle, with no JavaScript](https://osysharp.com/reference/ui/chart-kit/) — the charts whose series and furniture follow the theme.
