Palettes
The palette set has two tiers: **chromatic** palettes where the colour IS the
title: "Palettes" description: "The palette set has two tiers: chromatic palettes where the colour IS the element,"
The palette set has two tiers: chromatic palettes where the colour IS the
element, and surface-ring palettes — neutral surfaces whose only difference
from surface is a chromatic focus ring, so a field's frame stays neutral
while its focus states the theme's colour. Plus the two neutral surfaces that
everything sits on. Every palette declares all six roles in both light and
dark.
The neutral surfaces
Surface
The floor of the app. Neutral, the palette everything else sits on.
Raised
An elevated surface — card, popover, dialog. Like surface but a step up in lightness so a raised element reads as sitting above the floor.
The chromatic palettes
A chromatic palette is one where the colour is the element: base is
saturated, contrast is near-white or near-black against it, accent is a
darker/lighter version of the same hue for use on foreign backgrounds.
Blue
Hue ~250. The project's accent colour, used where the colour IS the element: CTA, badge, marked control. Every role carries the blue hue.
Green
Hue ~150. Confirmation, positive state — though the semantic-status set is temporarily withdrawn; green is a colour here, not a status.
Orange
Hue ~55, distinct from danger (hue ~25) so the two never read as the same state.
Purple
Hue ~300.
Rose
Hue ~345. Deliberately kept distinct from danger (hue ~25): rose reads as pink/magenta, not as an error state.
Danger
Hue ~25. Error, destructive action. Applied automatically by [data-invalid]
— Base UI's Field emits data-invalid on Field.Root, and because palette
roles are inherited via custom properties, the whole subtree becomes danger
with no extra class.
Do not use aria-invalid:palette-danger — Tailwind variants only apply to
utilities, and palette-* is a plain CSS class. That syntax compiles
nothing.
The surface-ring palettes
A surface-ring palette is a neutral surface first: base, soft, line,
contrast and accent are byte-identical to surface. The only role that
differs is ring, which is a literal chromatic value taken from the
corresponding chromatic palette's base. It exists so a field's frame can
stay neutral (a field is a surface, not a coloured element) while its focus
ring states the theme's colour. This is the architecture's own answer to "the
need is real (theme a field's focus), the answer is a new palette, not a
seventh role" — the same reasoning that justifies raised, applied sideways
to a single role instead of a whole tier.
Surface-blue
Neutral surface, blue focus ring (~250).
Surface-purple
Neutral surface, purple focus ring (~300).
Surface-green
Neutral surface, green focus ring (~150).
Surface-orange
Neutral surface, orange focus ring (~55).
Surface-rose
Neutral surface, rose focus ring (~345).
Writing a palette
A palette is a CSS class that declares six custom properties:
:root[data-theme="light"] .palette-my-blue {
--palette-base: oklch(0.55 0.2 250);
--palette-soft: oklch(0.95 0.03 250);
--palette-line: oklch(0.85 0.08 250);
--palette-contrast: oklch(1 0 0);
--palette-accent: oklch(0.45 0.18 250);
--palette-ring: var(--palette-accent);
}
:root[data-theme="dark"] .palette-my-blue {
--palette-base: oklch(0.7 0.18 250);
--palette-soft: oklch(0.3 0.08 250);
--palette-line: oklch(0.4 0.1 250);
--palette-contrast: oklch(0.12 0.02 250);
--palette-accent: oklch(0.78 0.15 250);
--palette-ring: var(--palette-accent);
}Both themes, all six roles, no exceptions. Add the palette name to the cn
helper's palette class group so cn("palette-surface", "palette-my-blue")
resolves to one.