Themes

How the override model works and how to create your own theme.

The Override Model

A theme is a partial set of token overrides scoped under a [data-theme] CSS selector. Themes never create new raw values — they remap at the alias or system tier by picking different steps on the raw scales the base already provides.

A theme that overrides nothing is identical to the base. This means every theme inherits all 762 tokens and only needs to override the ones it wants to change.

css
/* Base (no data-theme attribute needed) */
:root {
  --color-primary: var(--color-neutral-900);
  --button-radius: var(--radius-md);
}

/* Custom theme — scoped override */
[data-theme="ocean"] {
  --color-primary: var(--color-blue-600);
  --button-radius: var(--radius-sm);
}

How data-theme Selectors Work

Set data-theme on any ancestor element. All descendants inherit the overridden token values through CSS cascade. Set it on <html> for global theming, or on any container for scoped theming.

tsx
// Global theme
<html data-theme="ocean">

// Scoped theme (only children inherit)
<div data-theme="ocean">
  <Button>Ocean-styled button</Button>
</div>

Token Override JSON Structure

Theme overrides are JSON files with the same structure as the base tokens. Only include the tokens you want to change — everything else is inherited.

Alias Overrides

Remap semantic roles to different raw values. This is the most powerful lever — a single alias change cascades to every component that references it.

src/alias/color.json
// src/alias/color.json — remap primary to blue
{
  "color": {
    "primary": { "$value": "{color.blue.600}", "$type": "color" },
    "primary-hover": { "$value": "{color.blue.700}", "$type": "color" }
  }
}

System Overrides

Fine-tune individual component tokens. Use this when you need component-specific tweaks beyond what alias changes provide.

src/system/button.json
// src/system/button.json — smaller radius for this theme
{
  "button": {
    "radius": { "$value": "{radius.sm}", "$type": "dimension" }
  }
}

Building a Theme with Style Dictionary

Theme packages use the same Style Dictionary v5 build pipeline as the base tokens. The key difference is scoping all output under a [data-theme] selector.

build.ts
import StyleDictionary from 'style-dictionary';

const sd = new StyleDictionary({
  source: ['src/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'dist/css/',
      files: [{
        destination: 'variables.css',
        format: 'css/variables',
        options: {
          outputReferences: true,
          selector: '[data-theme="ocean"]',
        },
      }],
    },
  },
});

await sd.buildAllPlatforms();

Creating a New Theme

1. Create the override files

Create JSON files under src/alias/ and src/system/ that override the tokens you want to change. Reference raw tokens that already exist in the base.

2. Add fonts (optional)

Create css/fonts.css with your font imports, and override the display/text font family tokens.

3. Build with Style Dictionary

Run Style Dictionary with outputReferences: true and scope output under [data-theme="yourname"].

4. Consume it

tsx
// Import after base tokens
import '@toucan-ui/tokens/css';
import './theme-ocean.css';

// Apply globally
<html data-theme="ocean">

// Or scope to a container
<div data-theme="ocean">
  <Button>Themed button</Button>
</div>

Example Themes

The example pages in this docs site demonstrate theming in action. Each example page applies a different set of token overrides to show how the same components adapt to different visual styles — all without changing any component code.