Sakani

Theming

How tokens flow from raw values to components, and how to re-theme the system.

Sakani's tokens are organized in two layers, and the direction of that flow matters: components only ever bind to the semantic layer, never to primitives directly.

1. Primitives

Raw scales with no semantic meaning attached: color/neutral/500, space/16, radius/md. These exist so the semantic layer has something consistent to alias, and are never referenced by component CSS directly.

2. Semantic tokens

Names like bg/surface, fg/muted, and accent/default alias primitives and carry both a light and a dark value. This is the only layer components bind to, so a rebrand or theme change means repointing a semantic token, and every component using it updates automatically.

TokenPreviewUsed for
--color-bg-canvasPage background
--color-bg-surfaceCard / panel background
--color-bg-subtleHover states, subtle fills
--color-fg-defaultPrimary text
--color-fg-mutedSecondary text
--color-border-defaultDefault borders
--color-accent-defaultPrimary action fill
--color-danger-solidDestructive actions, errors

Dark mode

Every semantic token has a value under :root (light) and another under .dark. Never hardcode a color in your own code that sits alongside Sakani components: bind to the semantic variable instead, and dark mode comes for free.

Re-theming

Because every component reads from the semantic layer, overriding a handful of variables after tokens.css loads re-themes the entire library, no component source changes, no rebuild.

/* Your app's own stylesheet, loaded after tokens.css */
:root {
  --color-accent-default: #6d28d9; /* swap the brand color... */
  --color-accent-hover: #5b21b6;
}
/* ...every Button, Switch, and focus ring using --color-accent-*
   updates immediately -- no component code changes. */