The default. Cool, technical, quiet.
cyanFoundations
A theme is an attribute, not a stylesheet. One palette changes the brand accent across every component; one typeface attribute re-points the sans, display and mono roles. Both are scoped, so they apply to the document — or to a single card.
Every palette below is a live subtree: the same Public API, the same tokens, one data-palette attribute. Nothing is re-implemented for the preview and nothing here needs JavaScript to render.
The default. Cool, technical, quiet.
cyanAI products, creative tools, consumer.
violetEnterprise software and infrastructure.
cobaltHealth, growth, sustainability.
emeraldCommerce, community, publishing.
roseWarm editorial, high energy.
emberMonochrome — the content is the colour.
graphiteWhy a sparkline is in the card
The line between brand and meaning is the whole reason the palette layer is small.
Changes with the palette
accent.default · hover · active · strongaccent.soft — the tint behind a selected or secondary surfacefg.accent — accent-coloured textfocus.ring — the focus language follows the brandsurface.selected · line.accent · glow.*chart.marker · chart.selection · chart.bandNever changes with the palette
A palette that repainted “negative” green would break every product built on Luc UI. That is why the status set has no palette override at all — the token file simply does not offer one.
Two pairs can break when an accent moves: the label on a filled accent control, and accent text on the default surface. Both are computed from the declared oklch values in unit tests, then re-measured in the browser by axe.
LUC Cyan
Electric Violet
Cobalt
Emerald
Rose
Ember
Graphite
The three columns are the same numbers the unit test asserts. Values are WCAG 2.1 contrast ratios computed from the declared colour; the darker figure is the light theme, the lighter figure the dark theme. Every pair clears 4.5:1 in both themes — the floor for body-size text.
Components never name a family. They use one of three roles — sans, display, mono — and a preset re-points all three at once. A consumer with licensed brand fonts points the variables at their own stack and never edits a component.
Inter
interNeutral, complete, self-hosted.
The role system means an editorial product can set body copy in a serif with looser leading while the data column stays monospaced and tabular, without a single component knowing which face is which.
Humanist system
humanistNo webfont: the platform's own sans.
The role system means an editorial product can set body copy in a serif with looser leading while the data column stays monospaced and tabular, without a single component knowing which face is which.
Editorial serif
editorialSerif voice with looser leading.
The role system means an editorial product can set body copy in a serif with looser leading while the data column stays monospaced and tabular, without a single component knowing which face is which.
Technical mono
technicalMonospaced display for engineering surfaces.
The role system means an editorial product can set body copy in a serif with looser leading while the data column stays monospaced and tabular, without a single component knowing which face is which.
Everything above is downstream of three attributes and one token layer.
<html data-theme="dark" data-palette="violet" data-typeface="editorial">/* A subtree theme is an attribute on any element. */
<section data-palette="graphite" data-theme="light">
<PricingTable />
</section>data-palette — one of the seven accent families. Status colours are deliberately not part of it.data-typeface — a preset that re-points the type roles. Point --font-sans, --font-display and --font-mono at your own stack to add one.data-density and data-motion sit alongside them and compose freely: they are the same mechanism, not a second theming API.Adding a palette
packages/ui/src/tokens/tokens.css, keep the lightness trajectory, change the hue — and the completeness and contrast tests will tell you if the ramp is incomplete or a label colour fails.Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
`ThemeProvider` is the mechanism under everything on this page: it writes the three attributes, resolves `system` against the operating system, and exposes `useTheme` to the product.
| Export | Kind | Signature |
|---|---|---|
ThemeProvider | 4 documented props | ({ children, defaultTheme, storageKey, attribute, }: ThemeProviderProps) => import("@types/react").JSX.Element |
useOptionalTheme | export | () => ThemeContextValue | null |
useTheme | export | () => ThemeContextValue |
Type: ThemeProviderProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
attribute? | "class" | "data-theme" | undefined | — | Attribute written on `<html>`. `class` drives the `dark:` variant. |
children | ReactNode | — | |
defaultTheme? | ThemeMode | undefined | — | |
storageKey? | string | undefined | — |