Storybook addon
@component-anatomy/storybook — an "Anatomy" panel next to Controls and Actions, synced two-way with the story canvas.
Compatibility#
Storybook 10 and 11, any renderer (React, Vue, HTML, Web Components…). The canvas side is framework-agnostic — it only reads the rendered DOM, open shadow roots included (see Web Components).
The package is ESM-only, like Storybook itself. Storybook 9 is no longer supported — CSF Next registration needs definePreviewAddon, which Storybook only ships from 9.1 onwards.
Installation#
npm install --save-dev @component-anatomy/storybook
// .storybook/main.ts
export default {
addons: ['@component-anatomy/storybook'],
};
That’s the whole setup — the addon registers its panel and a global decorator automatically.
With CSF Next#
CSF Next is the default story format in Storybook 11. A preview.ts built with definePreview composes only the addons it lists, and Storybook drops the preview annotations main.ts would otherwise contribute — so the addon has to be registered there too, or the canvas decorator never mounts and the panel stays empty:
// .storybook/preview.ts
import { definePreview } from '@storybook/your-framework';
import componentAnatomy from '@component-anatomy/storybook';
export default definePreview({
addons: [componentAnatomy()],
});
Registering in both places is correct and safe: the two paths are mutually exclusive, so the decorator is composed exactly once either way. A preview.ts that is still a plain object needs only the main.ts entry above. Registering in preview.ts also types parameters.anatomy across that preview’s metas and stories.
Usage#
Annotate the story’s DOM with data-part and add the anatomy parameter:
export const Anatomy: Story = {
parameters: {
anatomy: {
parts: [
{ id: 'icon', name: 'Icon', description: 'Optional leading glyph.' },
{ id: 'label', name: 'Label', description: 'The visible action text.' },
{ id: 'badge', name: 'Badge', description: 'Numeric counter.' },
],
},
},
};
Hover a part in the panel → the canvas element is highlighted. Hover the element in the canvas → the panel entry activates. Omit parts entirely and the panel lists what it finds in the DOM.
Parameters#
| Key | Type | Description |
|---|---|---|
parts |
AnatomyPartDefinition[] |
Part list. Omit to auto-discover from data-part. |
preset |
string |
default |
theme |
AnatomyTheme |
Token overrides for the overlays and the panel accent, e.g. { accent: "#0d9488" }. |
overlayLabel |
boolean |
Show the floating name chip. Default true. |
overlayPadding |
number |
Inflate highlight boxes by N px. |
shadowParts |
boolean |
Also read the native part attribute inside web components’ shadow roots. Default false. |
root |
string |
CSS selector narrowing the anatomy root inside the canvas. |
disable |
boolean |
Turn the addon off for a story. |
Parameters follow Storybook’s normal inheritance: set project-wide defaults in .storybook/preview.ts, per-component defaults in meta.parameters, and per-story overrides in story.parameters. Typed as AnatomyParameters:
import type { AnatomyParameters } from '@component-anatomy/storybook';
export const Anatomy: Story = {
parameters: {
anatomy: { preset: 'blueprint', overlayPadding: 2 } satisfies AnatomyParameters,
},
};
In MDX docs pages#
The same table renders inside an MDX page with the <Anatomy> block, so component anatomy can sit in the prose next to the preview rather than only in the panel. It needs @storybook/addon-docs, which is what renders MDX:
import { Meta } from '@storybook/addon-docs/blocks';
import { Anatomy } from '@component-anatomy/storybook/blocks';
import * as ButtonStories from './Button.stories';
<Meta of={ButtonStories} />
<Anatomy of={ButtonStories.Anatomy} />
<Anatomy> renders the canvas and the table together, tightened into one pairing, with the canvas’s show-code button dropped by default. Hover sync works both ways, exactly as it does in the panel. Point of at a story to read its parameters.anatomy, at a whole CSF module to read the meta’s, or omit it on an attached docs page to fall back to the page’s current story.
| Prop | Type | Description |
|---|---|---|
of |
CSF export |
Story or meta to document. Omit on an attached page for the current story. |
parts |
AnatomyPartDefinition[] |
Curated list, overriding the parameter and auto-discovery. |
sync |
boolean |
Two-way hover sync with the canvas. Default true. |
sourceState |
'hidden' | 'shown' | 'none' |
Passed to the internal <Canvas>. Default 'none' — set it to keep the show-code button. |
For a hand-placed canvas — a custom layout, or one that keeps its source panel some other way — use <AnatomyTable> next to your own <Canvas> instead. It takes the same of, parts and sync props, minus sourceState, which is <Canvas>’s concern:
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { AnatomyTable } from '@component-anatomy/storybook/blocks';
import * as ButtonStories from './Button.stories';
<Meta of={ButtonStories} />
<Canvas of={ButtonStories.Anatomy} />
<AnatomyTable of={ButtonStories.Anatomy} />
Auto-discovery reads the story as it renders, so a block relying on it needs a canvas on the same page — <Anatomy> brings its own, <AnatomyTable> needs a <Canvas> or <Story> block next to it. A block with an explicit parts list stands on its own. Several blocks can share one page: each talks only to the story it names.
Live examples#
The deployed Storybook shows Button (default + blueprint theme), Slider (accent theme) and Tabs (auto-discovery, high-contrast preset, and that preset combined with a brand accent); a Web Components group with vanilla custom elements, Lit components, Shoelace’s switch and Ionic’s toggle (built with Stencil, lazy-loaded); plus an Edge cases group covering the states where there is nothing to show — no parts, disabled, unconfigured, a closed shadow root — and a component defined late, and two MDX pages, Docs → Anatomy in MDX and the Button docs page, using the <Anatomy> block. Sources live in examples/storybook in the repo.