Skip to content

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.

LiveOverview

Workspace settings

Changes save to every member.

Pro plan

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);
}

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 */
  }
}