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.
| Token | Preview | Used for |
|---|---|---|
| --color-bg-canvas | Page background | |
| --color-bg-surface | Card / panel background | |
| --color-bg-subtle | Hover states, subtle fills | |
| --color-fg-default | Primary text | |
| --color-fg-muted | Secondary text | |
| --color-border-default | Default borders | |
| --color-accent-default | Primary action fill | |
| --color-danger-solid | Destructive 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. */