Addon
Wiring swatchbook into Storybook is this package’s job. It loads your config at build time through @unpunnyfuns/swatchbook-core, exposes the resolved graph as a virtual module, and at preview time registers a decorator and a manager toolbar, and exposes the useToken hook. The MDX doc blocks live in @unpunnyfuns/swatchbook-blocks, which this package depends on and re-exports; importing either package’s name gets you the blocks.
Install
Section titled “Install”npm install -D @unpunnyfuns/swatchbook-addon @unpunnyfuns/swatchbook-coreThis gives you the toolbar, preview decorator, and the useToken() hook. The addon depends on @unpunnyfuns/swatchbook-blocks and re-exports its full surface (TokenTable, ColorPalette, TokenDetail, TokenNavigator, Diagnostics, SwatchbookProvider, block-side hooks), so no separate install is needed inside a Storybook that already registers the addon; import from either package’s name for the same re-exported surface. Install @unpunnyfuns/swatchbook-blocks directly only for a non-Storybook consumer of the blocks. See the authoring guide for MDX composition patterns.
Peer requirements: Storybook 10.1+ on the Vite builder, React 18+.
Registration
Section titled “Registration”Register the addon in main.ts#addons with your config (inline config or configPath; see the config reference for the trade-off):
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', }, }, }, ],});Then opt the preview into the addon’s annotations (CSF Next factory) in preview.ts:
import { definePreview } from '@storybook/react-vite';import swatchbookAddon from '@unpunnyfuns/swatchbook-addon';
export default definePreview({ addons: [swatchbookAddon()],});Factory options
Section titled “Factory options”swatchbookAddon(options?) accepts a presenters option, a PresenterRegistry merged over the built-ins per $type. One registration reaches every built-in block in both story renders and provider-less MDX doc blocks:
export default definePreview({ addons: [swatchbookAddon({ presenters: { color: DualSurfaceSwatch } })],});This is distinct from the preset AddonOptions passed in main.ts (config / configPath / integrations). See Creating custom presenters for the presenter contract and Presenter registry for the full override precedence.
What it registers
Section titled “What it registers”- Preview decorator: reads the active axis tuple, writes one
data-<prefix>-<axis>="<context>"attribute per axis on<html>+ story wrapper, mounts the per-theme CSS, emitsINIT_EVENTto the manager. The prefix followscssVarPrefix(defaultswatch). - Toolbar tool: a single swatchbook
IconButton. Clicking opens a popover containing preset pills and one dropdown per axis. Escape and outside-click close it.
For a browsable tree + diagnostics surface, compose <Diagnostics /> + <TokenNavigator /> + <TokenTable /> on an MDX page. See the authoring guide’s dashboard example. The Diagnostics reference catalogs every group and message that can appear in the Design Tokens panel.
Globals
Section titled “Globals”| Key | Type | Purpose |
|---|---|---|
swatchbookAxes | Record<string, string> | Active tuple. Preferred input. |
The toolbar writes swatchbookAxes. Blocks’ starting color format comes from Config.defaultColorFormat instead of a toolbar control; hex falls back to space-separated rgb() (flagged with a ⚠ warning marker) for colors outside sRGB gamut. See useColorFormat for the full precedence order.
Per-story overrides
Section titled “Per-story overrides”export const DarkBrandA = meta.story({ parameters: { swatchbook: { axes: { mode: 'Dark', brand: 'Brand A' }, }, },});axes is a tuple; omitted keys fall back to axis defaults.
The addon exposes exactly one hook, under @unpunnyfuns/swatchbook-addon/hooks.
useToken(path)
Section titled “useToken(path)”Typed lookup. Returns { value, cssVar, type?, description? } for the given path; the value is typed as unknown, type and description come from the token’s $type / $description if present. Reactive to axis changes.
import { useToken } from '@unpunnyfuns/swatchbook-addon/hooks';
function Brand() { const accent = useToken('color.accent.bg'); return <span style={{ background: accent.cssVar }}>{accent.description}</span>;}Paths autocomplete from the generated .swatchbook/tokens.d.ts. Add "include": [".swatchbook/**/*.d.ts"] to your consumer tsconfig.
Reach for useToken, the doc blocks, or the Design Tokens panel rather than importing virtual:swatchbook/tokens directly: the virtual module is internal wiring and its shape isn’t a stable API. Prefer var(--…) in CSS where you can; useToken().cssVar returns the same reference string for programmatic use.
Block-side hooks
Section titled “Block-side hooks”The block-side hooks (useSwatchbookData, useActiveTheme, useActiveAxes, useColorFormat) are re-exported from the addon’s main entry, so import { useActiveTheme } from '@unpunnyfuns/swatchbook-addon' works without a separate blocks dependency; they’re documented in the blocks hooks reference. The preview decorator mounts the SwatchbookProvider that backs them, so they resolve wherever the addon is registered.
Exported constants
Section titled “Exported constants”import { ADDON_ID, AXES_GLOBAL_KEY, TOOL_ID, VIRTUAL_MODULE_ID } from '@unpunnyfuns/swatchbook-addon';Useful when wiring the addon into custom tooling or writing interaction tests that read the globals directly.
AddonOptions
Section titled “AddonOptions”Typed shape of the options object passed to the Storybook addons entry: what the preset reads at preview start.
import type { AddonOptions } from '@unpunnyfuns/swatchbook-addon';
interface AddonOptions { /** Inline swatchbook config. Mutually exclusive with `configPath`. */ config?: Config; /** Path to a config module, relative to the Storybook `configDir`. */ configPath?: string; /** * Display-side integrations plugged into the addon's Vite plugin. * Each typically contributes a virtual module the preview imports * (e.g. `virtual:swatchbook/tailwind.css`). The addon itself is * tool-agnostic; integrations ship as separate packages. */ integrations?: SwatchbookIntegration[];}Config and SwatchbookIntegration come from @unpunnyfuns/swatchbook-core. See the Registration section above for a worked example, and the Integrations guide for integrations factories.