Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

color palettes

Primary = Palette.From("#0077B6"); … Bg = Primary.Hover; Bg = Primary[600];

`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.

stable8 examples compiled by CIuithemecolorpalette

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.

theme Default {
  Colors {
    Primary = Palette.From("#0077B6");   // one seed → a whole ramp
  }
}

Signature#

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#

One seed, a full ramp#

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).

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#

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:

[Composable] component Fill() {
  variants { base { Bg = Colors.Primary; } }   // Bg is exactly #0077B6
  render { Box(); }
}

Like any theme tokens 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#

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:

[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(); }
}
StepWhat it's for
Subtlea faint tint — a hover background, a selected row
Muteda soft fill — a chip, a well
Defaultthe palette's mid shade
Hoverone step stronger than the base — a hover/emphasis fill
Activestronger still — a pressed state
Strongthe darkest useful shade — high-emphasis text or borders
OnColorthe foreground color to place on the palette — chosen automatically (black or white) for legible contrast against the base
Textthe 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.

[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#

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:

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#

A palette step is an ordinary style value, so it works anywhere a color does — a theme token:

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#

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−1th 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:

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) — 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#

Related

theme tokens

A `theme` block names your app's design tokens — colors, spacing, radii, and more — as reusable values. A token can…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…