Storybook stories
SkillFiles & storageConventions for writing recompose Storybook stories. Use when creating or reviewing any *.stories.tsx file, the Storybook config, or the fake bridge decorator.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Storybook stories skill
What this skill tells your AI
The instructions your AI receives, as published by recomposesh/recompose in .claude/skills/storybook-stories/SKILL.md and read by ahel’s review.
Placement
- A story lives next to its component:
<component>.stories.tsxinside the owning slice'sui/segment. - Import other slices only through their public
index.ts. Steiger enforces this in stories too. - A play function queries with
findBy, nevergetBy, whenever the component reads a suspended query.getByis synchronous, so it throws the moment the story mounts, while the boundary is still pending and the canvas holds no roles at all. The vitest project hides this, because its render flushes React's act queue; Chromatic does not, and reported it asUnable to find an accessible elementwithThere are no accessible rolesagainst a screenshot that plainly showed the control. Measured: the first role appears about 340 milliseconds in, well insidefindBy's one second. - An assertion that something is absent needs a settle point before it.
queryBy(...)).toBeNull()passes on an empty canvas, so it proves nothing until anawait canvas.findBy...for something present has run first. - A focus assertion waits too.
findByresolves the moment the element exists, which is before the effect that places focus has run: measured at 0 milliseconds focus sits onbody, and reaches the control around 50. Hold the element in a variable, then asserttoHaveFocusinsidewaitFor. - A dark-scheme story carries no play function. It exists to be looked at, by a person and by the snapshot tool. Asserting the scheme inside it tests the capture environment rather than the component, and the environments disagree: the vitest project applies the toolbar's class, a static build applies nothing and lets
prefers-color-schemedecide, and the capture tool emulates the media. Measured across all three, the same assertion failed three different ways. The semantics belong in the light stories, which run everywhere. - A new
ui/component and its story land together, before the branch leaves your machine. Writing the component without the story is the mistake this rule exists to stop, and it stays cheap only while both files are still open. - Two gates enforce it.
pnpm run lint:storiesruns onpre-pushand names each component missing its sibling, so the branch never reaches a pull request with the gap. The pull-request meta-gate runs the same script against the pull request's base. Escape:stories-exemptlabel plus aStories-exempt: <reason>body line, and the escape only exists on the pull request.
Format
- Component Story Format (CSF) factories only:
import preview from '#.storybook/preview', thenpreview.meta({ component })andmeta.story({ args }). - One concept per story. Split a story that shows two ideas. A
SizesAndVariantsstory mixing many concepts is the documented anti-pattern;Basic,Primary,Disabledis the documented good shape.
Manifest documentation
Agents consume stories through manifests built by static analysis (source: Storybook AI best practices and manifests):
- JSDoc with a purpose sentence goes on every exported component, prop, and story. This is manifest documentation for agents, the one sanctioned exception to the no-comments rule.
- Describe the why, not the what: when to reach for the component or variant, never how it renders.
- Agents read a component's
@summarytag, or a truncated description when no summary exists. A story surfaces roughly its first sixty characters, so front-load the point; add@summarywhen a description runs long. - Prop tables come from docgen. This repo pins
reactDocgen: 'react-docgen'(the TypeScript-aware extractor crashes against the TypeScript 7 compiler), so hand-written prop JSDoc carries more weight here than upstream docs assume. - Curate, never dump: irrelevant manifest content degrades agent output as surely as missing content. Tag anti-pattern or deprecated stories with
tags: ['!manifest']on the story, on the meta (whole file), or on an MDXMetatag. - Manifests capture only what static analysis sees. A dynamically computed value never lands in them, so write key values literally in MDX docs pages.
- Sanity-check what agents see at
/manifests/components.jsonand the human-readable/manifests/components.htmlwhilestorybook devruns.
Wired components
- Components touching TanStack Query or the bridge render through the global
withRecomposeBridgedecorator automatically. - Scenario data goes through parameters:
parameters: { bridge: { accounts, overrides } }whereoverridesis aPartial<RecomposeIpc>. - Never talk to the real bridge or network in a story.
Accessibility
- Story tests run axe with
parameters.a11y.test: 'error'. Fix the component, not the gate. - A story-level opt-out (
parameters: { a11y: { test: 'off' } }) needs the reason in the story's JSDoc.
Verification
RECOMPOSE_BROWSER_TESTS=1 pnpm --filter @recompose/desktop exec vitest run --project storybookruns every story as a browser test. Without the variable the project stays closed, because the Chromium suites are opt-in locally (record 0176).pnpm --filter @recompose/desktop run storybookserves the workshop on port 6006, with the MCP endpoint at/mcp.
Look at it, every time
Any change that reaches the screen gets opened in the browser through claude-in-chrome, in both schemes, before it lands. That covers a component, a story, a design token, and the Storybook config. Load the affected stories at http://localhost:6006/iframe.html?id=<story-id>&globals=theme:light and again with theme:dark.
This isn't ceremony. On the run that wrote this rule every gate was green, axe included, while all of the following shipped:
- The dark scheme rendered light. The theme class reached the root element, and the app stylesheet gave
bodyits owncolor-scheme, which beats what it inherits. - A row printed its label twice, because one control named itself visibly while its three siblings used
aria-label. - An inert row looked identical to a live one, since only its control carried the state.
- A selected segment sat at 1.05 to 1 against its track, carried by a shadow alone.
- Every group story rendered at 1654 pixels, three times the width the layout contract fixes, so nobody had seen the real proportions.
Axe passed all five, because each one is a fact about appearance rather than about semantics. A suite that never looks can't catch them.
Measure the close calls rather than squinting. Read computed style straight from the page, and compute the ratio when a state indicator or a muted ink is in question. Three of those five were only provable by number, and one of them looked fine in a screenshot.
Signals
- GitHub stars
- 27
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
storybook-stories-recomposesh- Source
- github.com/recomposesh/recompose