Component Anatomy
Storybook addon — interactive component anatomy panel synced with the story canvas
@component-anatomy/storybook
Storybook addon that adds an Anatomy panel — an interactive part list synced two-way with the story canvas.
- Hover a part in the panel → the element is highlighted in the canvas
- Hover a
data-partelement in the canvas → the panel entry activates - The same table renders inside MDX via
@component-anatomy/storybook/blocks - Works with Storybook 9 and 10, any renderer (React, Vue, HTML, Web Components…)
Install
npm install --save-dev @component-anatomy/storybook
// .storybook/main.ts
export default {
addons: ['@component-anatomy/storybook'],
};
Use
Annotate your story's DOM with data-part and add the anatomy parameter:
import type { AnatomyParameters } from '@component-anatomy/storybook';
export const Anatomy: Story = {
parameters: {
anatomy: {
parts: [
{ id: 'icon', name: 'Icon', description: 'Optional leading glyph.' },
{ id: 'label', name: 'Label', description: 'The visible action text.' },
],
// optional:
preset: 'blueprint', // 'default' | 'minimal' | 'contrast' | 'blueprint'
theme: { accent: '#0d9488' }, // theme tokens for the canvas overlays
overlayLabel: true, // floating name chip
overlayPadding: 2, // inflate highlight boxes (px)
root: '.my-component', // narrow the anatomy root (CSS selector)
disable: false, // turn off for a story
} satisfies AnatomyParameters,
},
};
Omit parts (pass {}) and the panel lists parts auto-discovered from data-part attributes, with names derived from the ids.
preset and theme reach the panel and the <Anatomy> block too, not just the canvas overlays: the active row is accented with the same color the overlay paints. Where that color would be illegible on Storybook's own panel — contrast is black, the manager is dark by default — the table falls back to the most colorful alternative that meets WCAG AA, so contrast accents with its yellow on a dark panel and with black on a light one.
Parameters follow Storybook's normal inheritance — project-wide defaults in .storybook/preview.ts, per-component in meta.parameters, per-story overrides in story.parameters.
In MDX
The panel's table also renders inline in a docs page, so anatomy can sit in the
prose next to the preview. Requires @storybook/addon-docs (the addon that
renders MDX), which stays an optional peer dependency — the panel above works
without it.
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 (chrome that
mostly restates what the table already documents). Hover sync works both ways,
exactly as in the panel.
| Prop | Type | Description |
|---|---|---|
of |
CSF export | The story to document, or a whole CSF module to read the meta's parameters. Omit on an attached docs page (one with <Meta of={…} />) to fall back to the page's current story. |
parts |
AnatomyPartDefinition[] |
Curated list, overriding both parameters.anatomy.parts and auto-discovery. |
sync |
boolean |
Two-way hover sync with the rendered story. Default true. false gives a purely static table. |
sourceState |
'hidden' | 'shown' | 'none' |
Passed to the underlying <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} />
Two things worth knowing, for either block:
- Auto-discovery needs the story's canvas on the page. Parts are read from
the story's
data-partattributes as it renders, so a block that relies on discovery needs a canvas on the same page —<Anatomy>brings its own,<AnatomyTable>needs a<Canvas of={…} />(or<Story of={…} />) next to it. A block with an explicitpartslist stands on its own. - A meta has no canvas.
of={ButtonStories}readsmeta.parameters.anatomy, but there is nothing to discover parts from or to sync hover with — give itpartsexplicitly, and use<AnatomyTable>rather than<Anatomy>since there is no story to draw a canvas from.
Several blocks can share one page; each talks only to the story it names.
Example
A complete Storybook 10 setup with Button/Slider/Tabs stories — and two MDX
pages using the <Anatomy> block — lives in examples/storybook, deployed at https://julien-deramond.github.io/component-anatomy/storybook/.
Docs
Full documentation: https://julien-deramond.github.io/component-anatomy/docs/storybook
License
MIT
- julien-deramond