| name | build-stories |
| source | botcore |
| description | Guides creation of Storybook 10 stories using CSF3 format, Custom Elements Manifest (CEM) integration, play function interaction tests, visual testing, and accessibility checks for web components. Covers story file structure, naming conventions, autodocs configuration, addon-vitest headless test loops, addon-a11y, coverage reporting, and shadow DOM assertion patterns. Use when writing stories, adding interaction tests to stories, configuring Storybook for web components, debugging play functions, or setting up story-based visual and accessibility testing. Triggers: storybook, story, stories, csf3, cem, custom elements manifest, play function, interaction test, visual testing, addon-vitest, autodocs.
|
| version | 1.0.0 |
| triggers | ["storybook","story","stories","csf3","cem","custom elements manifest","play function","interaction test","visual testing","addon-vitest","autodocs"] |
| portable | true |
Building Stories
Storybook 10 story authoring with CSF3, Custom Elements Manifest autodocs, play function interaction tests, and headless visual testing for web components.
Capabilities
- CSF3 story authoring -- Write Component Story Format 3 stories for web components using the
@storybook/web-components framework
- Custom Elements Manifest (CEM) -- Generate and use
custom-elements.json for automatic prop tables and autodocs
- Play function testing -- Turn stories into executable interaction tests using
storybook/test utilities
- Headless story tests -- Run story play functions headlessly via Vitest browser mode with addon-vitest
- Accessibility testing -- Validate WCAG compliance within Storybook using addon-a11y
- Coverage reporting -- Generate code coverage from story tests via Vitest v8 provider
- Shadow DOM assertions -- Test web components with encapsulated shadow roots using correct query patterns
Routing Logic
Core Principles
1. Every component gets a story
Each component should have a co-located *.stories.ts file. Stories serve triple duty: visual documentation, interactive examples, and automated tests via play functions.
2. CSF3 is the only format
All stories use Component Story Format 3. Each file exports a default meta object and named story exports. The satisfies Meta pattern provides type safety.
import type { Meta, StoryObj } from "@storybook/web-components";
const meta = { } satisfies Meta;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = { args: { } };
3. Play functions are tests
Every story should include a play function with expect assertions. Play functions run in the browser -- both interactively in Storybook UI and headlessly via npm run test:stories.
4. Shadow DOM requires direct queries
Testing Library's within() and getByRole() cannot pierce shadow boundaries. Always use el?.shadowRoot?.querySelector() for web components. This is the single most common mistake in story tests.
5. CEM drives autodocs
The Custom Elements Manifest (custom-elements.json) provides prop tables, slot documentation, and event listings in autodocs pages. Always regenerate CEM after changing component APIs.
Workflow
Writing a new story
- Create
{component-name}.stories.ts next to the component source
- Import the component side-effect style (
import "./my-element.js")
- Define meta with
title, component (tag name string), and tags: ["autodocs"]
- Add
parameters.docs.description.component for the autodocs page
- Write a
render function using html tagged template from lit
- Export named stories with
args and play functions
- Run
npm run test:stories to verify play functions pass
Testing loop
npm run cem:analyze
npm run storybook
npm run test:stories
npx vitest run --config vitest.storybook.config.ts --coverage
Story tests run as part of pre-push hooks and CI checks.
Quick story template
import type { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import { expect, userEvent } from "storybook/test";
import "./my-element.js";
const meta = {
title: "Category/My Element",
component: "my-element",
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: "Brief description of the component.",
},
},
},
render: (args) => html`<my-element .title=${args.title}></my-element>`,
} satisfies Meta;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: { title: "Hello" },
play: async ({ canvasElement }) => {
const el = canvasElement.querySelector("my-element");
await expect(el).toBeTruthy();
const heading = el?.shadowRoot?.querySelector("h2");
await expect(heading?.textContent).toContain("Hello");
},
};
See references/csf3-patterns.md for full patterns including argTypes, decorators, and multi-variant stories.
Play function quick reference
import { expect, userEvent, fn, within } from "storybook/test";
const el = canvasElement.querySelector("my-element");
await expect(el).toBeTruthy();
const label = el?.shadowRoot?.querySelector(".label");
await expect(label?.textContent).toContain("Expected text");
const button = el?.shadowRoot?.querySelector("button") as HTMLElement;
await userEvent.click(button);
const updated = el?.shadowRoot?.querySelector(".active");
await expect(updated).toBeTruthy();
See references/play-functions.md for complete assertion patterns, event simulation, and anti-patterns.
Checklist
When to Escalate
- Play functions pass in Storybook UI but fail headlessly (check timing, async rendering, or browser-specific APIs)
- CEM does not pick up new properties or events (verify JSDoc placement is above the decorator, not between decorator and class)
- Autodocs page is blank despite
tags: ["autodocs"] (ensure @storybook/addon-docs is in .storybook/main.ts addons)
- Shadow DOM structure changes after a web component library upgrade (update all
shadowRoot?.querySelector selectors)
- Testing Library queries silently return null instead of finding shadow DOM elements (switch to direct
shadowRoot queries)
- Story tests are flaky due to async icon loading or animation timing