Theming recipes
Because every oks-ui token is a CSS variable, most theming problems are a few lines of CSS in the right scope. These are the patterns that come up most: copy, paste, adjust.
Workspace settings
Changes save to every member.
Pick a recipe on the left and it runs here.
The one idea behind all of these: a token set on an element cascades to every oks-ui component inside it. Put your override on :root and it's global; put it on a wrapper and it's scoped to that subtree. That's the whole mechanism. If you need the conceptual reference first, start with the overview.
1. Scope a theme to one section
Give one region a different accent without a provider or a re-render — set the ramp on a wrapper instead of :root. Everything inside re-tints; everything outside is untouched. Set every shade, 50 to 950: components read different shades for different states (soft fills read 50 and 100, solid fills and text read 700, hover reads 800 and pressed reads 900), so a partial ramp leaves those states on the default colour and a hover can flash the old brand.
<section className="promo">
{/* Buttons, Chips, links here use the promo accent */}
<Button color="primary">Upgrade</Button>
</section>/* point the role at a built-in palette, so dark mode follows too */
.promo {
--oks-color-primary-50: var(--oks-palette-emerald-50);
--oks-color-primary-100: var(--oks-palette-emerald-100);
--oks-color-primary-200: var(--oks-palette-emerald-200);
--oks-color-primary-300: var(--oks-palette-emerald-300);
--oks-color-primary-400: var(--oks-palette-emerald-400);
--oks-color-primary-500: var(--oks-palette-emerald-500);
--oks-color-primary-600: var(--oks-palette-emerald-600);
--oks-color-primary-700: var(--oks-palette-emerald-700);
--oks-color-primary-800: var(--oks-palette-emerald-800);
--oks-color-primary-900: var(--oks-palette-emerald-900);
--oks-color-primary-950: var(--oks-palette-emerald-950);
}style object on the wrapper. This is exactly how the theme switcher on the home page works: it sets --oks-color-primary-* on a preview div, no library re-render.2. White-label / multi-brand
Ship one build, switch brands with a single attribute on <html> (or any ancestor). Each brand is a block of variable declarations; the runtime just toggles data-brand.
[data-brand="astrobit"] {
--oks-color-primary-50: #eff6ff;
--oks-color-primary-100: #dbeafe;
--oks-color-primary-200: #bfdbfe;
--oks-color-primary-300: #93c5fd;
--oks-color-primary-400: #60a5fa;
--oks-color-primary-500: #3b82f6;
--oks-color-primary-600: #2563eb;
--oks-color-primary-700: #1d4ed8;
--oks-color-primary-800: #1e40af;
--oks-color-primary-900: #1e3a8a;
--oks-color-primary-950: #172554;
--oks-radius-md: 8px;
}
[data-brand="bilkoss"] {
--oks-color-primary-50: #fdf2f8;
--oks-color-primary-100: #fce7f3;
--oks-color-primary-200: #fbcfe8;
--oks-color-primary-300: #f9a8d4;
--oks-color-primary-400: #f472b6;
--oks-color-primary-500: #ec4899;
--oks-color-primary-600: #db2777;
--oks-color-primary-700: #be185d;
--oks-color-primary-800: #9d174d;
--oks-color-primary-900: #831843;
--oks-color-primary-950: #500724;
--oks-radius-md: 2px;
}// flip the whole app's brand at runtime
document.documentElement.setAttribute("data-brand", "bilkoss");3. Recolour one component only
When you want a single element off-palette — a neutral-ink primary button, a warning-coloured input — use the component's own hooks instead of a global token. Nothing else changes. A solid button's fill is --oks-button-tone-text, with -hover and -active variants for those states; setting --oks-button-tone alone does not change it.
{/* an ink button that ignores the primary role entirely */}
<Button
style={{
"--oks-button-tone-text": "var(--oks-palette-neutral-900)",
"--oks-button-tone-text-hover": "var(--oks-palette-neutral-800)",
"--oks-button-tone-text-active": "var(--oks-palette-neutral-950)",
} as React.CSSProperties}
>
Continue
</Button>
{/* or a deeper shade of the same role */}
<Button color="primary" colorDepth={600}>Save</Button>4. Compact density preset
Dashboards and data-dense screens want tighter spacing and sharper corners. Because padding and radius are tokens, a "compact" mode is a scope with smaller values — no per-component prop churn.
.density-compact {
--oks-space-2: 0.3rem;
--oks-space-3: 0.45rem;
--oks-space-4: 0.6rem;
--oks-radius-md: 3px;
--oks-radius-lg: 5px;
}This tightens padding and corners; a button's height comes from its size, so use the component-specific hooks on top when you need it, e.g. --oks-button-height and --oks-button-padding-x on a toolbar of buttons.
5. High-contrast variant
An accessibility-minded theme deepens text and borders and drops soft tints toward solid. Scope it behind a class or a media query.
@media (prefers-contrast: more) {
:root {
--oks-color-border: var(--oks-palette-neutral-500);
--oks-form-field-border: var(--oks-palette-neutral-600);
--oks-color-primary-700: #1e1b4b; /* deeper: solid fills and text read this shade */
}
}700 shade and puts its label on it (white in light mode). Whatever brand ramp you drop in, make sure that 700 clears 4.5:1 against its label — pick a deeper shade if it doesn't.