Build documentation that developers and designers actually read — interactive Storybook stories, MDX design guidelines, usage do-and-dont patterns, and automated doc freshness checks.
Build documentation that developers and designers actually read — interactive Storybook stories, MDX design guidelines, usage do-and-dont patterns, and automated doc freshness checks.
Every Storybook story serves two masters: visual testing and documentation. Structure stories to do both well without duplication:
1// components/Button/Button.stories.tsx
2import type { Meta, StoryObj } from '@storybook/react';
3import { Button } from './Button';
4
5const meta: Meta<typeof Button> = {
6 title: 'Components/Button',
7 component: Button,
8 // Autodocs generates API reference from this
9 tags: ['autodocs'],
10 parameters: {
11 docs: {
12 description: {
13 component:
14 'The primary interactive element. Use for actions that change state ' +
15 'or navigate to a new page. Prefer `variant="primary"` for the main ' +
16 'call to action on a page — limit to one per screen.',
17 },
18 },
19 },
20 argTypes: {
21 variant: {
22 description: 'Visual style. Use primary for the main CTA, ghost for secondary actions.',
23 control: 'select',
24 options: ['primary', 'secondary', 'ghost', 'destructive'],
25 },
26 isLoading: {
27 description: 'Shows loading spinner. Disables the button and announces state to screen readers.',
28 },
29 },
30};
31
32export default meta;
33type Story = StoryObj<typeof Button>;
34
35// Named stories = test cases + documentation examples
36export const Primary: Story = {
37 args: { variant: 'primary', children: 'Save changes' },
38};
39
40export const Destructive: Story = {
41 args: { variant: 'destructive', children: 'Delete account' },
42 parameters: {
43 docs: { description: { story: 'Use only for irreversible destructive actions. Always pair with a confirmation step.' } },
44 },
45};MDX pages in Storybook blend prose, code, and live components — ideal for design guidelines that need both explanation and interactive examples:
1{/* components/Button/Button.mdx */}
2import { Canvas, Controls, Meta, Story } from '@storybook/blocks';
3import * as ButtonStories from './Button.stories';
4
5<Meta of={ButtonStories} />
6
7# Button
8
9The Button component triggers actions and navigations. It communicates
10interactivity through color, shape, and label.
11
12## When to use
13
14- **Primary**: The single most important action on the page (one per screen)
15- **Secondary**: Supporting actions with equal visual weight
16- **Ghost**: Low-priority actions that should not compete with primary
17- **Destructive**: Irreversible actions — always confirm before executing
18
19## Interactive demo
20
21<Canvas of={ButtonStories.Primary} />
22<Controls of={ButtonStories.Primary} />
23
24## Do and Don't
25
26| Do | Don't |
27|---|---|
28| Use one primary button per screen | Stack multiple primary buttons |
29| Write action verbs: "Save", "Submit" | Write nouns: "Submission", "Action" |
30| Show loading state during async operations | Remove the button while loading |
31
32## Accessibility
33
34All Button variants meet WCAG 2.1 AA contrast ratios. Icon-only buttons
35require an `aria-label` prop — the component renders a console warning
36in development if it is missing.Storybook autodocs generates API reference tables from TypeScript types automatically. Enable it per component with the autodocs tag:
1// In your story file
2const meta: Meta<typeof Dialog> = {
3 tags: ['autodocs'],
4 // argTypes override autodocs display
5 argTypes: {
6 onClose: {
7 description: 'Called when the dialog closes via Escape key or overlay click.',
8 action: 'onClose',
9 },
10 initialFocusRef: {
11 description: 'Ref to the element that receives focus when the dialog opens. Defaults to the close button.',
12 control: false, // Hide from controls panel — it's a ref
13 },
14 },
15};For complex prop types (union types, overloaded generics), document them explicitly in the argTypes description rather than relying on autodocs inference. TypeScript complex types often render as Record<string, unknown> in the generated table without additional description.
Keep argTypes descriptions in sync with the implementation using a JSDoc comment on the TypeScript interface — tsdoc-to-argTypes tools can sync these automatically:
1interface ButtonProps {
2 /** Visual style tier. Use 'primary' for the main CTA, max one per screen. */
3 variant?: 'primary' | 'secondary' | 'ghost' | 'destructive';
4 /** When true, disables interaction and announces loading to screen readers via aria-busy. */
5 isLoading?: boolean;
6}Documentation drifts from code when teams move fast. Catch drift automatically by linking docs to tests:
1// tests/docs-freshness.test.ts
2import { describe, it, expect } from 'vitest';
3import { readdirSync, readFileSync } from 'fs';
4import { join } from 'path';
5
6describe('Documentation freshness', () => {
7 it('every component has a corresponding MDX file', () => {
8 const componentsDir = 'src/components';
9 const components = readdirSync(componentsDir);
10
11 for (const component of components) {
12 const mdxPath = join(componentsDir, component, `${component}.mdx`);
13 expect(
14 () => readFileSync(mdxPath),
15 `Missing docs: ${component}/${component}.mdx`
16 ).not.toThrow();
17 }
18 });
19
20 it('every exported prop has an argTypes description', () => {
21 // Parse stories and check argTypes for description coverage
22 // This runs as a Vitest test so it runs in CI
23 const storyFiles = glob.sync('src/**/*.stories.tsx');
24 for (const file of storyFiles) {
25 const content = readFileSync(file, 'utf8');
26 const undocumentedProps = findArgTypesWithoutDescription(content);
27 expect(undocumentedProps, `${file} has undocumented props`).toHaveLength(0);
28 }
29 });
30});A design system serves designers, developers, and product managers differently. Structure your documentation entry points accordingly:
1// .storybook/main.ts — organize stories by audience
2const STORY_ORDER = [
3 'Intro',
4 'Getting Started/For Developers',
5 'Getting Started/For Designers',
6 'Getting Started/For Product',
7 'Foundations/Tokens',
8 'Foundations/Typography',
9 'Foundations/Color',
10 'Foundations/Spacing',
11 'Components/**',
12 'Patterns/**',
13 'Changelog',
14];
15
16export default {
17 stories: ['../src/**/*.stories.@(ts|tsx|mdx)'],
18 storySort: {
19 order: STORY_ORDER,
20 },
21};For Designers: Create MDX pages showing Figma embeds alongside live component previews so the design spec and implementation are always side by side. For Developers: Code snippets with copy button, TypeScript autocomplete hints, and version compatibility tables. For Product: Pattern libraries showing assembled UI patterns (forms, onboarding flows, dashboards) built from components, with links to relevant user research. Measure documentation quality by tracking search terms used in Storybook's search box — high-frequency failed searches reveal documentation gaps.
Pick your most-used component and rewrite its story file to include component-level description, per-story descriptions, and a full MDX guideline page with Do-and-Dont patterns. Measure how often that documentation page is viewed over the next sprint.
Your progress is saved in this browser, on this device only.