From Storybook Workbench
Write a CSF3 story for ONE React component, covering only its materially-different states (no Cartesian), with a factory when 3+ stories share a shape. Use for 'write a story for X', 'document this component', 'add a Storybook story'.
How this skill is triggered — by the user, by Claude, or both
Slash command
/storybook-workbench:sb-storiesThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
The default Build mode. The component exists in `src/components/`; you write its visible-states story.
CONTEXT.mdagents/openai.yamlreferences/anti-patterns.mdreferences/composition-patterns.mdreferences/directory-structure.mdreferences/extraction-workflow.mdreferences/factory-patterns.mdreferences/test-wiring.mdreferences/validate-workflow.mdreferences/with-mcp.mdreferences/without-mcp.mdscripts/check-story-ready.shscripts/discover-runtime.pyscripts/extract-prop-shapes.shscripts/extract-states.shscripts/page-patterns.pyscripts/scaffold-factory.shscripts/scaffold-page-story.pyscripts/validate-stories.shtemplates/controlled-component-story.tsxThe default Build mode. The component exists in src/components/; you write its visible-states story.
components.real[] in .storybook/project-inventory.json. If it's
in dead[], remove it — don't write a story. If it's vendor (shadcn ui/), deprioritize.references/anti-patterns.md (Button 8, Input 8, Modal 5, Form 6).
Read .storybook/component-states.json instead of guessing — if it's missing, generate it first:
scripts/extract-states.sh. (Bigger extraction context: references/extraction-workflow.md.)
To render all those states on one canvas, use the StateGrid wrapper (variants × states →
StateMatrix) from sb-wrappers — don't hand-roll the grid. These are the component-time wrappers;
the data wrappers (ProjectInventory / DesignSystemHealth / AppFlowGraph …) come after their own steps..storybook/component-usage.json (sb-inventory's
extract-component-usage.sh — generate it if missing). Use props.<prop>.values to prioritize the
variants real call sites pass; for anything in declaredButUnused (e.g. variant=danger never used),
still author the state for completeness but tag it ['usage:unused'] and note "not used in this app"
— don't pad the catalog with states prod never renders.UsageSection block (wired once into
preview.ts docs.page by sb-setup) renders a "Where it's used" map per component — the pages it
lands on, what nests it, what it renders, the tokens it pulls — read from the usage graph
(component-pages.json). So when you add a story, don't hand-add a usage block; just run
refresh-usage.sh so the graph is current and the band populates. To explore the whole graph
interactively (any token / component / page → everywhere it's used, clickable), scaffold the
UsageExplorer wrapper (sb-wrappers). Details + the docs.page composition live in sb-inventory
(§ "Real usage in autodocs") and sb-setup (docs-page composition)..storybook/prop-shapes.json (candidates
flagged with liveUsages) — if missing, run scripts/extract-prop-shapes.sh first. Then
scripts/scaffold-factory.sh <Type> <import-path> and fill the deterministic stub
(references/factory-patterns.md); otherwise inline args.storySort.order; if the project has none, pick one via
references/directory-structure.md.play actually earn its place? Only write one for an interaction, async data,
a portal, a CSS-driven state, or accessibility — never a bare toBeVisible() (anti-pattern 34).
And the project needs exactly one CssCheck (anti-pattern 33): one story asserting a real
getComputedStyle token value, the only proof the shared preview loaded the app's CSS. Both come
from npx storybook ai setup's prompt; validate-stories.sh check 13 + the project tally enforce them.args (variant, size, state) in the Controls tab and the canvas re-renders. It is
NOT canvas interaction; hover/focus/active live in a StateGrid interaction matrix (addon-pseudo-states),
and scripted click→assert flows live in a play function. So: Controls = try props · pseudo-states =
hover/focus columns · play = a real interaction. Say which one the story is.react-docgen) does NOT infer TS unions into selects, so declare argTypes for every enum/union
prop (control: 'select'|'inline-radio' + options), group with table.category, hide
escape-hatch / non-serializable props (className, refs, icon / callback / data props), and disable
controls on render-only showcase stories (parameters: { controls: { disable: true } }). Full
pattern + the docgen gotcha: references/without-mcp.md §13. (The panel itself must be visible —
sb-setup writes a manager.ts with showPanel: true; sb-audit's audit-controls.sh flags any
component story missing this wiring.)grep -q '@storybook/addon-mcp' package.json && test -f .mcp.json && echo WITH_MCP || echo WITHOUT_MCP
WITH_MCP → references/with-mcp.md (MCP injects CSF3 conventions; you focus on judgment).WITHOUT_MCP → references/without-mcp.md (13 verification gaps + 4 critical SB10 patterns).templates/controlled-component-story.tsx — the useArgs sync is what AI gets wrong.Where the file goes (ASK first — don't scatter the repo). Read storiesLocation from
.storybook/audit/status.md (the single rule lives in CONTEXT.md §STORIES LOCATION).
sb-setup never asked), STOP and ASK
the user before writing any story — never guess, never co-locate silently. Use AskUserQuestion
(Claude) / request_user_input (Codex), or a numbered list where no blocking tool exists:
Where should I save the stories? (everything else already lives under
.storybook/.)
.storybook/stories/(recommended — one place, isolated;src/untouched, one removable folder)- Co-located
src/**/<Name>.stories.tsx(for a project you own long-term)- A custom folder (you name it — still kept to that one place) Then record it in
.storybook/audit/status.mdasstoriesLocation: <isolated|colocated|PATH>, make suremain.tsstoriesincludes that path, and proceed. Recommend option 1.
isolated (or .storybook/stories/) → write under .storybook/stories/ mirroring the tree
(.storybook/stories/components/CourseCard.stories.tsx), importing the component via the @/ alias.colocated → src/components/<X>/<X>.stories.tsx. A custom path → write there, every story.Whatever the answer, all stories go to that one location — never a mix.
Title (the in-Storybook path, separate from the file path): match .storybook/preview.ts
storySort.order; else Components/<Domain>/<Name>. An Overview / Spec / hub story is an entry
point — pin it to the top of its root via storySort.order (a root-level hub first; a per-feature
Overview/Spec first in its group's sub-order with a '*' tail), never let it sort alphabetically into
the middle of the content it summarizes. See references/directory-structure.md → "Overview / spec / hub".
For a page (a route/view under pages/ · app/ · routes/ · views/) do NOT recreate it.
Run scripts/page-patterns.py <root> first — per page it reports importable, component,
layout, dataHook + dataType (the mock signal), sections (render order, ui/ excluded),
gridHint; plus sharedSections[]. Then pick the mode off importable:
importable: true (page has a default-export component) → import the real page as-is and
mock ONLY its data layer. Scaffold with scripts/scaffold-page-story.py <root> <page-suffix>:
it emits a Pages/<Name> story that imports the real page + wires the detected provider
(Inertia usePage / router / store), with props seeded from a factory keyed on dataType
(scripts/scaffold-factory.sh <dataType> <import-path>, per references/factory-patterns.md).
Add one story per materially-different data state (empty / populated / error) — different
factory inputs, never different markup. Never re-author the page's JSX (anti-pattern 27):
the layout, columns, and components are the real page's, not yours.importable: false (assembled inline / no single component) → fall back to Page Composition
(references/composition-patterns.md Pattern 4): assemble from the real sections —
still factory-backed, still real components.sharedSections[] (a section rendered by ≥2 pages) are the reusable page-pattern pieces — give each
its own Components/* story so pages compose them, not duplicate them.
An open overlay portals a position:fixed inset-0 overlay to document.body. Rendered inline on
the autodocs page (as the Primary block does), that overlay escapes over the whole Docs page —
Title, the "Real usage" section, Controls all vanish behind a blank backdrop. So for any overlay
component with autodocs, scope the story to its own iframe:
parameters: {
layout: 'fullscreen',
docs: { story: { inline: false, height: '640px' } }, // portal stays inside the frame; Docs prose stays readable
}
The story view (one story, full canvas) is unaffected — this is only for the Docs page. Don't reach for it on non-overlay components (inline rendering is lighter).
The page's own dataHook is just its data; the preview must also supply the provider TREE + root
CSS the page renders under. Read those from .storybook/runtime.json (scripts/discover-runtime.py) —
providers[], rootCss, portals[], network.needsMsw — they're set up once in the shared preview
by sb-setup, so a page story rarely re-wires them. Never re-derive by shell scan what a script
already wrote to .storybook/*.json — cite the field.
Write one story per component (each covering only its real states), then gate each with
scripts/validate-stories.sh. On Claude Code you can speed a batch up by writing components in
parallel with the Agent tool, but it's the same work — no special sub-agent needed.
SKILL=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
CORE=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
"$CORE/scripts/validate-stories.sh" path/to/Foo.stories.tsx
# --strict adds tsc + eslint · --diff lints only changed stories
# For the "give me confidence" verdict (setup readiness preflight + conformance in one),
# run "$SKILL/scripts/check-story-ready.sh" path/to/Foo.stories.tsx instead — CONFIDENT when discovery JSONs are present.
Exits non-zero on any FAIL — fix before continuing. If you wrote a play, also dispatch the
judgment sub-agent (see references/validate-workflow.md); bash can't verify a play
is meaningful. To make stories an agent-runnable CLI gate (headless vitest + a11y), see
references/test-wiring.md.
npx claudepluginhub strongeron/storybook-workbench --plugin storybook-workbenchGenerates CSF3 Storybook stories for components (React, Vue, shadcn/ui) with variant coverage and state matrices. Invoke when creating component stories.
Guides writing, updating, and reviewing Storybook stories with a strict workflow. Pulls story-writing rules from the MCP, ensures a running preview, and prevents unverified stories.
Creates and reviews Storybook component documentation with stories, MDX, decorators, interaction tests, and visual baselines. Useful for setting up design system presentations or isolated component state previews.