Skip to content

Repository files navigation

react-anatomy — beautifully annotate component slots with no effort

A Storybook addon that draws anatomy diagrams for your components. Mark the parts with data-slot, set one story parameter, and every part gets a labelled callout in a gutter outside the frame — reached by a leader that provably never crosses another.


Design-system docs live and die by their anatomy diagrams, and today they are drawn by hand in Figma — re-measured and re-laid-out every time a component changes. react-anatomy draws them right in Storybook, from the live DOM. Turn it on for a story and it discovers the parts, lays out the labels, routes the leaders, and outlines each region. Nothing is hand-placed, so nothing goes stale.

A card component with media, heading, body and footer slots each labelled in the left gutter

What you get

  • Zero coupling. The addon reads data-slot attributes off the rendered DOM. It never imports your components and never needs to know what they are.
  • Leaders that cannot cross. Every leader leaves its region perpendicular to the nearest rail; because each fan on a side spans one x-interval, order is preserved and crossings are impossible — a geometric guarantee, not a heuristic.
  • Deterministic. The placement is a pure function of the geometry, the label sizes, and a set of constants. Same input, identical output, every render — diagrams that are safe to snapshot.
  • The component never moves. Gutters are reserved before the layout solves, so turning the overlay on doesn't shift the thing you're documenting by a pixel.
  • Navigable or static. Leave it navigable and the reader drills a level at a time with breadcrumbs; pin a depth for a fixed diagram to embed in docs.

Install

npm install @react-anatomy/storybook

Setup

Register the addon's decorator in your Storybook preview. It derives the label from the story context, so there is nothing to name by hand:

// .storybook/preview.ts
import { decorators } from "@react-anatomy/storybook/preview";

export default { decorators };

Usage

Mark the parts of your component with data-slot — that is the entire contract:

function Card({ children }) {
  return (
    <div>
      <div data-slot="media">{/* … */}</div>
      <div data-slot="heading">{/* … */}</div>
      <div data-slot="body">{/* … */}</div>
      <div data-slot="footer">{/* … */}</div>
    </div>
  );
}

Then set the slotAnnotations parameter on a story. On its own it opens on the outermost slots and lets the reader drill in a level at a time — click a part to dive into it, and the breadcrumb trail walks back out. Every level is re-labelled from scratch, leaders and all. boundary outlines the component's own edge:

export const Anatomy = {
  parameters: { slotAnnotations: { boundary: true } },
  render: () => <Card>{/* … */}</Card>,
};

Clicking into the card's heading, then into its text, then back out through the breadcrumb — each level re-labelled with non-crossing leaders

Pin a depth instead for a static diagram. "all" labels the full tree at once — nested regions and all — in the same non-crossing construction:

export const AnatomyAll = {
  parameters: { slotAnnotations: { depth: "all" } },
  render: () => <Card>{/* … */}</Card>,
};

The same card with every nested slot labelled at once — media, badge, title, icon, text, heading, subtitle, body, footer, button-primary and meta

Or focus on one part with scope — the labels route out to the sides when the parts are packed together in a row:

export const HeadingAnatomy = {
  parameters: { slotAnnotations: { scope: "heading" } },
  render: () => <Card>{/* … */}</Card>,
};

The heading region zoomed in, its icon, text, attribute, badge and actions labelled below

Parameters

Set slotAnnotations to true for the outermost slots, or to an object:

Option Type Default Description
scope string outermost Annotate the slots inside the element carrying this data-slot.
depth number | "all" navigable Nesting levels to show. Omit for the drill-down; set for a static diagram.
boundary boolean false Outline the component's own edge.
gutters "reserved" | "fitted" "reserved" "fitted" crops the gutters to the labels (needs a pinned depth).

The root breadcrumb is taken from the story context — the scope when there is one, otherwise the last segment of the story title.

Working in the repo

pnpm install
pnpm build      # build the packages
pnpm test       # the placement + collection suites (vitest)
pnpm lint       # eslint (--max-warnings=0) + prettier + knip + depcheck
pnpm storybook  # the playground, consuming the built packages

Requires Node 24 and pnpm 11.8.0 (provisioned via devEngines).

MIT licensed.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages