- name
- stories-documentation
- description
- Full authoring procedure for the per-unit MDX docs page of gen2 components, internal components, patterns, and controllers: documentation structure, the Helpers section, section patterns (overview, anatomy, options, states, behaviors, accessibility), 1st-gen to gen2 comparison, verification against source so docs don't claim attributes, slots, or ARIA that don't exist, and general writing guidelines. Use when writing, migrating, or reviewing a gen2 `<unit>.mdx` docs page.
# Per-unit MDX authoring procedure
This skill holds the full procedure. The always-apply constraints are in `.ai/rules/stories-documentation.md`, which loads automatically for matching files.
## Documentation structure
Organize MDX into these sections (skip sections that don't apply):
1. **Overview** - rendered automatically by `<DocsHeader />` from the meta JSDoc, subtitle, and Overview story
2. **Anatomy** - A flat, unordered list of the component's parts, with slot names called out inline where needed (components and patterns)
3. **Usage** - How to instantiate and configure (controllers)
4. **Options** - Configuration, variants, sizes, styles
5. **States** - Different component states
6. **Behaviors** - Methods, events, automatic behaviors
7. **Accessibility** - Features and best practices
8. **Upcoming features** - Roadmap intent (prose only, no Canvas)
9. **API** - Rendered automatically by `<DocsFooter />`. Controllers hand-author a `## API` section first.
10. **Feedback** - Rendered automatically by `<DocsFooter />`.
The remaining sections of this rule describe the **prose content** for each section. Author that prose inside the per-unit MDX, not as JSDoc above stories.
## Helpers section
**Purpose**: Organize shared code, label mappings, and utilities used across multiple stories.
**Location**: Between `export default meta` and first story (Playground/Autodocs).
**When to use:**
- Component has multiple variants, sizes, or options that need consistent labels
- Stories require reusable label mappings or data structures
- Complex rendering logic needs to be shared across stories
**Pattern**:
```typescript
// ────────────────────
// HELPERS
// ────────────────────
// Label mappings for sizes
const sizeLabels = {
s: 'Small',
m: 'Medium',
l: 'Large',
xl: 'Extra-large',
} as const satisfies Record<ComponentSize, string>;
// Label mappings for semantic variants
const semanticLabels = {
positive: 'Approved',
negative: 'Rejected',
informative: 'Active',
neutral: 'Archived',
notice: 'Pending',
} as const satisfies Record<SemanticVariant, string>;
// Combined label objects for convenience
const allLabels = { ...semanticLabels, ...colorLabels };
```
**Key principles:**
- Use TypeScript `as const satisfies` for type-safe label mappings
- Group related helpers together (size labels, variant labels, etc.)
- Use clear, descriptive constant names (`sizeLabels`, not `labels`)
- Provide realistic, meaningful labels (not "Label 1", "Label 2")
**When NOT to use:**
- Component has only 1-2 options (no need for label mappings)
- Labels are self-explanatory (e.g., true/false)
- Stories don't share any common code
## Section patterns
> **Where the prose lives.** Each pattern below shows a "prose" block. Author that prose as Markdown inside the corresponding `## Section` / `### Story Title` in the `<unit>.mdx` file, immediately above the `<Canvas of={Stories.MyStory} />` reference. The accompanying story definition has no JSDoc above it.
### Overview
**Purpose**: Quick introduction showing the component in its most common use case.
**Documentation**: The Overview story content comes from two sources:
1. **JSDoc description above meta**: Provides the main description of what the component does and when to use it. This description:
- Is displayed as the primary documentation for the Overview story
- Can include markdown formatting and links to other components
- Should reference other components using Storybook paths: `[Badge](../?path=/docs/components-badge--docs)`
- Should provide fuller context than the subtitle
2. **`parameters.docs.subtitle`**: Provides a brief summary displayed as the subtitle. This subtitle:
- Cannot include links (plain text only)
- Should be concise and to-the-point
- Should not repeat the JSDoc description verbatim
- Complements the JSDoc description
**Pattern**:
```typescript
/**
* A badge is a non-interactive visual label that displays a status, category, or attribute.
* Badges can be used to highlight important information or to categorize items. For interactive
* labels, see [Button](../?path=/docs/components-button--docs).
*/
const meta: Meta = {
title: 'Badge',
component: 'swc-badge',
args,
argTypes,
render: (args) => template(args),
parameters: {
docs: { subtitle: `Visual label for status, category, or attribute` },
// ... other parameters
},
tags: ['migrated'],
};
export default meta;
// ... HELPERS section if needed ...
export const Overview: Story = {
tags: ['overview'],
args: {
// Most common/representative configuration
},
};
```
**Note**: the Overview story renders into the docs page header via `<OverviewStory />` inside `<DocsHeader />`. The meta-level JSDoc (above `const meta`) and the `parameters.docs.subtitle` provide the surrounding prose; the Overview story itself just supplies the canvas.
### Anatomy
**Purpose**: Give the reader a mental model of the component's parts in plain language. Anatomy is not a second copy of the API table: the generated `<ApiTable />` (rendered by `<DocsFooter />`) already lists every slot and property from source with type and description. Anatomy should not duplicate it.
**Required content:**
- A flat, unordered list of the component's visual parts
- Slot names are called out **inline** only in places where a reader needs to know a piece of content comes from a slot (so they write it as markup) rather than a property (so they write it as an attribute); not a separate slot inventory
- Visual examples showing structure
**Consolidation rule**: Combine all slotted content combinations into a **single Anatomy story**.
**MDX prose** (`## Anatomy` section uses `hideTitle` semantics: no per-story `###`, and no `###`/`####` subsections of any kind):
```mdx
## Anatomy
A component-name consists of:
- **Primary element**: main visual component
- **Secondary element**, provided via the `icon` slot
- **Optional indicator**: shown conditionally
<Canvas of={Stories.Anatomy} />
```
**Story definition** (no JSDoc above it):
```typescript
export const Anatomy: Story = {
render: (args) => html`
${template({ ...args /* minimal */ })}
${template({ ...args /* with icon */ })}
${template({ ...args /* with additional content */ })}
`,
tags: ['anatomy'],
parameters: {
flexLayout: 'row-wrap',
},
};
```
**Key principles:**
- Use a flat, unordered list of parts. Never a numbered list: there is no numbered diagram for the numbers to key off of, and it reads oddly for single-part components. Never `###`/`####` subsections (e.g. no "Content", "Slots", or "Properties" headings). Doing so re-creates the API table by hand and drifts out of sync with it.
- Name a slot inline in the parts list only when a reader needs to know content is passed as a slot rather than a property to compose it correctly
- Don't re-list content-rendering properties that are already visible in the rendered example or covered by the API table
- Show all meaningful combinations in one story
### Options
**Purpose**: Document every attribute or property not covered in Anatomy, States, or Behaviors.
**Order**: Sizes → Semantic variants → Non-semantic variants → Quiet/Subtle/Emphasized → Outline → Static color → Positioning → Other options
**Consolidation rules**:
- All sizes → single `Sizes` story
- All semantic variants → single `SemanticVariants` story
- All non-semantic variants → single `NonSemanticVariants` story
**Layout requirement**: Use `flexLayout: 'row-wrap'` for stories displaying multiple variations.
**Pattern for sizes**:
```mdx
### Sizes
Component-names come in [X] sizes to fit various contexts:
- **Small (`s`)**: Used for inline indicators or space-constrained areas
- **Medium (`m`)**: Default size, used for typical use cases
- **Large (`l`)**: Used for prominent displays or primary content areas
- **Extra-large (`xl`)**: Maximum visibility (if applicable)
All sizes shown below for comparison.
<Canvas of={Stories.Sizes} />
```
```typescript
export const Sizes: Story = {
render: (args) => html`
${template({ ...args, size: 's', label: 'Small' })}
${template({ ...args, size: 'm', label: 'Medium' })}
${template({ ...args, size: 'l', label: 'Large' })}
`,
tags: ['options'],
parameters: {
flexLayout: 'row-wrap',
},
};
```
**Pattern for semantic variants**:
```mdx
### Semantic variants
Semantic variants provide meaning through color:
- **`accent`**: New, beta, prototype, draft
- **`informative`**: Active, in use, live, published
- **`neutral`**: Archived, deleted, paused, draft, not started, ended
- **`positive`**: Approved, complete, success, new, purchased, licensed
- **`notice`**: Needs approval, pending, scheduled
- **`negative`**: Error, alert, rejected, failed
All semantic variants shown below for comparison.
<Canvas of={Stories.SemanticVariants} />
```
```typescript
export const SemanticVariants: Story = {
render: (args) => html`
${template({ ...args, variant: 'positive', label: 'Positive' })}
${template({ ...args, variant: 'informative', label: 'Informative' })}
${template({ ...args, variant: 'negative', label: 'Negative' })}
${template({ ...args, variant: 'neutral', label: 'Neutral' })}
${template({ ...args, variant: 'notice', label: 'Notice' })}
`,
tags: ['options'],
parameters: {
flexLayout: 'row-wrap',
},
};
SemanticVariants.storyName = 'Semantic variants';
```
**Pattern for static color**: sourced from Spectrum's static color usage guidance (e.g. [Button](https://spectrum.adobe.com/page/button/#Static-color)). The component's color pins to the chosen value regardless of the active theme, and the choice of value depends on the background it sits over.
```mdx
Use `static-color` when the component-name needs to sit on top of a photo or colored background. It pins the component-name's color to the chosen value regardless of the active color theme:
- **`white`**: use on dark color or image backgrounds
- **`black`**: use on light color or image backgrounds
```
```typescript
export const StaticColors: Story = {
render: (args) => html`
${['white', 'black'].map(
(color) => html`
${template({ ...args, 'static-color': color })}
`
)}
`,
args: {
label: 'Loading',
},
tags: ['options', '!test'],
parameters: {
flexLayout: false,
staticColorsDemo: true,
},
};
```
### States
**Purpose**: Document all possible states the component can be in.
**Order**: Default → Selected → Active → Disabled → Readonly → Error → Loading/Pending/Indeterminate → Other states
**Consolidation rule**: Combine all states into a **single States story** when possible (or minimal stories when states are complex).
**Pattern** (single States story; section heading and story name match — collapse to one heading, no `### States`; see "Single-story sections"):
```mdx
## States
Components can exist in various states:
- **Default**: Normal, interactive state
- **Selected**: Item has been chosen or activated
- **Disabled**: Functionality exists but is not available
- **Error**: Validation failure or error condition
All states shown below for comparison.
<Canvas of={Stories.States} />
```
```typescript
export const States: Story = {
render: (args) => html`
${template({ ...args, label: 'Default' })}
${template({ ...args, selected: true, label: 'Selected' })}
${template({ ...args, disabled: true, label: 'Disabled' })}
${template({ ...args, invalid: true, label: 'Error' })}
`,
tags: ['states'],
parameters: {
flexLayout: 'row-wrap',
},
};
```
**Pattern for complex states** (when animation or interaction is critical — states no longer combine into one story, so each gets its own per-story `### Title`):
```mdx
### Indeterminate
The indeterminate state shows an animated loading indicator when progress is unknown or cannot be determined.
The animation automatically loops until the state changes.
<Canvas of={Stories.Indeterminate} />
```
```typescript
export const Indeterminate: Story = {
tags: ['states'],
args: {
indeterminate: true,
label: 'Loading...',
},
};
```
**Disabled state note** (fold into the `## States` prose above, or use verbatim if Disabled needs its own `### Disabled` story):
```mdx
A component in a disabled state shows that [functionality] exists, but is not available in that circumstance.
This can be used to maintain layout continuity and communicate that [functionality] may become available later.
**ARIA support**: When disabled, the component automatically sets `aria-disabled="true"`.
View on GitHub