Quickstart
Assumes an existing Storybook 10 project with the Vite builder. Install, register, author the first doc page.
Install
One package pulls the whole surface — toolbar, preview decorator, useToken(), every MDX doc block, the standalone ThemeSwitcher:
npm install -D @unpunnyfuns/swatchbook-addon
swatchbook-core, -blocks, and -switcher come along transitively. If you only need a slice (say, the switcher in a Docusaurus site), each is also published independently.
Peer requirements: Storybook 10.3+ on the Vite builder, React 18+, Node 24+.
Terrazzo dependencies
Swatchbook is built on Terrazzo's parser. @terrazzo/parser, @terrazzo/plugin-css, and @terrazzo/plugin-token-listing come along with swatchbook-core transitively — you don't install them separately for the default setup. swatchbook-core publishes @terrazzo/parser and @terrazzo/plugin-css as peer deps on top of its own deps, so pnpm hoists to a single shared parser instance across our internal build and any Terrazzo plugins you add yourself.
Two cases where you'll install more packages explicitly:
- You already run
@terrazzo/cliin production. Pin matching versions of@terrazzo/cli,plugin-css, etc. in your ownpackage.jsonso both pipelines agree on options. See Aligning with your token build for the shared-options module pattern. - You want per-platform names (Swift, Android, Sass, JS, …) shown in doc blocks. Install
@terrazzo/plugin-swift/-android/-sass/-jsat a version compatible with swatchbook's peer range, then load them viaconfig.terrazzoPlugins+config.listingOptions.platforms.<TokenDetail>'s Consumer Output picks them up automatically.
When in doubt, skip this section — the zero-config install is enough for everything on this page.
Configure
Register the addon with an inline config in .storybook/main.ts:
import { defineMain } from '@storybook/react-vite/node';
export default defineMain({
framework: '@storybook/react-vite',
addons: [
{
name: '@unpunnyfuns/swatchbook-addon',
options: {
config: {
resolver: 'tokens/resolver.json',
cssVarPrefix: 'ds',
},
},
},
],
});
Opt the preview into the addon's annotations (decorator, globals, CSS injection):
import { definePreview } from '@storybook/react-vite';
import swatchbookAddon from '@unpunnyfuns/swatchbook-addon';
export default definePreview({
addons: [swatchbookAddon()],
});
Inline config is the primary path. If you'd rather keep the config in its own file (useful when the same config is consumed by a CLI or CI lint outside Storybook), write a swatchbook.config.ts and point at it with options.configPath: '../swatchbook.config.ts' — see config reference for details.
If your stories use Tailwind or a CSS-in-JS library
Stories that already use CSS variables need no extra wiring — the swatchbook toolbar flips --<prefix>-* vars via cascade and the stories repaint automatically. Two library-specific shortcuts for everything else:
- Tailwind v4. Add
@unpunnyfuns/swatchbook-integrations/tailwindto the addon'sintegrations[]and@tailwindcss/viteto Storybook's Vite plugins. Utility classes likebg-<prefix>-surface-default/p-<prefix>-mdresolve through swatchbook's tokens automatically. - emotion / styled-components / any
ThemeProvider. Add@unpunnyfuns/swatchbook-integrations/css-in-jstointegrations[]. Storiesimport { theme } from 'virtual:swatchbook/theme'and hand it to their provider.
Both integrations are preview-only — they let utility classes and theme accessors resolve correctly inside Storybook, nothing else. See the Integrations guide for the full picture.
Start Storybook:
pnpm storybook
What you should see
Toolbar: a single Swatchbook icon button in the top bar. Click it to open a popover containing preset pills, one dropdown per modifier in your resolver, and a color-format picker. If you've written a single theme modifier with Light/Dark contexts, you get one dropdown inside the popover; if you have mode × brand, you get two. Escape or outside-click closes it.
Your first doc page
Create an MDX file under your stories glob. The addon's blocks render against whichever tuple the toolbar has active, so everything below re-renders live as you flip modifiers:
import { Meta } from '@storybook/addon-docs/blocks';
import {
ColorPalette,
Diagnostics,
TokenDetail,
TokenNavigator,
TokenTable,
} from '@unpunnyfuns/swatchbook-addon';
<Meta title="Docs/Tokens" />
# Tokens
<Diagnostics />
## Palette
<ColorPalette filter="color.palette.**" />
## Semantic roles
<ColorPalette filter="color.**" />
## Everything
<TokenNavigator initiallyExpanded={0} />
## Inspect a single token
<TokenDetail path="color.accent.bg" />
Each block takes filter/scoping props so you can pick what shows up:
<ColorPalette filter="…" />— swatch grid grouped by sub-path. Filter by a glob ("color.palette.*","color.brand.*").<TokenTable filter="…" type="…" />— searchable two-column table (path + value). Filter by path glob, scope by DTCG$type.<TokenNavigator root="…" type="…" initiallyExpanded={0..∞} />— expandable tree. Scope to a subtree (root="color") and/or a$type;initiallyExpanded={0}opens fully collapsed.<TokenDetail path="…" />— alias chain, per-theme values, CSS var reference, anduseTokensnippet for one token.<Diagnostics />— collapsible list of parser / resolver / validation warnings. Auto-opens when anything's non-zero.<TypographyScale>,<DimensionScale>,<FontFamilyPreview>,<FontWeightScale>,<BorderPreview>,<ShadowPreview>,<GradientPalette>,<MotionPreview>,<StrokeStylePreview>— type-specific blocks with built-in samples. Filter by path glob when you want a subset.
The blocks reference has the full prop list for each. The authoring guide walks through composing them in real pages.
Theme the block chrome against your tokens
The MDX doc blocks read their surfaces, text, and accent colours from a fixed --swatchbook-* CSS-variable namespace. By default, those variables fall back to light/dark literals that flip with the active color-scheme, which means the blocks render legibly before you wire anything up. To have them reflect your own tokens, supply a chrome map — one entry per role, each pointing at a token path in your project:
{
name: '@unpunnyfuns/swatchbook-addon',
options: {
config: {
resolver: 'tokens/resolver.json',
cssVarPrefix: 'ds',
chrome: {
surfaceDefault: 'color.surface.default',
surfaceMuted: 'color.surface.muted',
surfaceRaised: 'color.surface.raised',
textDefault: 'color.text.default',
textMuted: 'color.text.muted',
borderDefault: 'color.border.default',
accentBg: 'color.accent.bg',
accentFg: 'color.accent.fg',
bodyFontFamily: 'typography.body.font-family',
bodyFontSize: 'typography.body.font-size',
},
},
},
},
With a chrome map in place the blocks track every toolbar flip — switch brand or contrast and the block chrome repaints alongside your stories. The closed set of roles (and what each maps to internally) is in the chrome config reference.
No resolver yet?
If you haven't written a DTCG 2025.10 resolver, you can use the layered config form instead:
export default defineSwatchbookConfig({
tokens: ['tokens/**/*.json'],
axes: [
{
name: 'mode',
contexts: {
Light: ['tokens/themes/light.json'],
Dark: ['tokens/themes/dark.json'],
},
default: 'Light',
},
],
cssVarPrefix: 'ds',
});
Each context names an ordered list of file paths (or globs) that layer on top of the base tokens files for that context. See the config reference for the resolver vs layered trade-off.
Next
- Embed doc blocks in MDX stories to build a proper reference page.
- Read the axes reference for the runtime model behind the toolbar.
- Poke at the live Storybook to see the addon running against a real multi-axis fixture.