| name | ds-document-component |
| description | Use when writing or generating Storybook documentation for a Baloise Design System component — creates stories.ts, doc-config.ts, and six MDX subpages (Overview, Usage, Variants, Styling, Accessibility, Testing) using reusable Storybook blocks (ComponentLead, ComponentPublicMethods, ComponentParts, CanvasTabs, ComponentPageObject) for dynamic data binding to components.json |
Write Component Docs
Generates a complete documentation set for a component in docs/src/components/<component>/. The canonical reference for structure and style is the tag component (docs/src/components/tag/).
Each component gets exactly six MDX files plus two TypeScript support files:
| File | Purpose |
|---|
1-Overview.mdx | Canvas (type-dependent) + Controls + ComponentLead + ComponentPublicMethods |
2-Usage.mdx | When to use, do's/don'ts, UsageExamples |
3-Variants.mdx | All story variants with Canvas (type-dependent) |
4-Styling.mdx | ComponentParts + ComponentCssVariables + ComponentDesignTokens |
5-Accessibility.mdx | WCAG guidelines via A11yGuidelines |
6-Testing.mdx | ComponentPageObject — PO API table + example test + install guide |
<component>.stories.ts | Stencil story exports — both 🧩 (web component) and 🌍 (HTML/CSS) pairs |
<component>.doc-config.ts | Shared section/color/tabs config |
Do NOT create api.md (auto-generated by Stencil), .parts.svg, or any other files.
Key Rules
- No categories — Storybook title is always
Components/<ComponentName>/... with no intermediate category path.
- Color is always
purple for all components.
- Canvas component choice is type-dependent:
- Web Components only (WC only): Use
<CanvasWithCodePen of={Stories.Basic} sourceState="shown" /> — shows CodePen embed without HTML tab
- Hybrid or CSS-only: Use
<CanvasTabs htmlOf={Stories.BasicHtml} of={Stories.Basic} /> — shows both web component and HTML/CSS tabs
- Stories have dual variants: every story comes in a
🧩 Name (web component, ds-* tags) and a 🌍 Name (HTML/CSS classes only) pair.
4-Styling.mdx uses ComponentCssVariables and ComponentDesignTokens — NOT TokenOverview.
Title and storyId Conventions
stories.ts
title: 'Components/<ComponentName>/Variants'
MDX Meta titles
| File | Meta title pattern |
|---|
1-Overview.mdx | "Components/<Name>/<Name>" |
2-Usage.mdx | "Components/<Name>/Usage" |
3-Variants.mdx | "Components/<Name>/Variants/Overview" |
4-Styling.mdx | "Components/<Name>/Styling" |
5-Accessibility.mdx | "Components/<Name>/Accessibility" |
6-Testing.mdx | "Components/<Name>/Testing" |
Why <Name>/<Name> for Overview? Storybook uses the last path segment as the sidebar/search label. Using the component name as the last segment makes the search show "Button" (not "Documentation") as the primary result, while keeping the page correctly nested under Components/<Name> in the sidebar.
doc-config storyIds
tabs: [
{ label: 'Overview', storyId: 'components-<component>--<component>' },
{ label: 'Usage', storyId: 'components-<component>--usage' },
{ label: 'Variants', storyId: 'components-<component>--variants-overview' },
{ label: 'Styling', storyId: 'components-<component>--styling' },
{ label: 'Accessibility', storyId: 'components-<component>--accessibility' },
{ label: 'Testing', storyId: 'components-<component>--testing' },
]
Process
Step 1 — Gather context
Read these files to understand the component:
packages/core/src/components/<component>/<component>.tsx — props, events, parts, render output
packages/core/src/components/<component>/test/<component>.visual.html — story sections
Step 1a — Determine component type
Check the component type by examining the TSX file and visual.html:
- Web Components only (WC only): Component has no HTML/CSS class-based equivalent. The visual.html only shows web component usage (
<ds-*> tags).
- Hybrid: Component supports both web component (
<ds-*>) and HTML/CSS class-based variants (e.g., <div class="ds-*">).
- CSS-only: Component has no web component implementation, only HTML/CSS classes.
Ask the user if unclear: "Is this component WC only, hybrid, or CSS-only?"
Step 2 — Present story sections
Show ALL data-testid sections from visual.html as a numbered list:
Which sections should become story variants?
1. basic
2. with-icon
3. colors
4. sizes
...
(enter numbers separated by commas, or "all")
Wait for user selection before generating anything.
<component>.stories.ts
Structure stories to expose component props as controls. All props go into the args object and are rendered via ${props(args)}:
import type { JSX } from '@baloise/ds-core'
import type { Meta } from '@storybook/html-vite'
import { createCssMappings, cssClasses, props, StoryFactory, withComponentControls, withRender } from '../../utils'
type Args = JSX.Ds<Component> & { slot: string }
const tag = 'ds-<component>'
const css = createCssMappings(tag)
const meta: Meta<Args> = {
title: 'Components/<ComponentName>/Variants',
args: {
slot: 'Default content',
},
argTypes: {
...withComponentControls({ tag: 'ds-<component>' }),
},
...withRender(({ slot, ...args }) => `<ds-<component> ${props(args)}>${slot}</ds-<component>>`),
}
export default meta
const Story = StoryFactory<Args>(meta)
export const Basic = Story({})
Basic.storyName = '🧩 Basic'
export const BasicHtml = Story({})
BasicHtml.storyName = '🌍 Basic'
export const WithVariant = Story({
args: {
variant: 'success',
},
})
WithVariant.storyName = '🧩 With Variant'
export const WithVariantHtml = Story({
args: {
variant: 'success',
},
})
WithVariantHtml.storyName = '🌍 With Variant'
Key patterns:
- Meta includes default
withRender — covers both WC and HTML/CSS default rendering via ${props(args)}
- Each story has an
args object — maps to component @Prop() values (e.g., { border: true, horizontal: true })
- Use
${props(args)} in template — serializes args to HTML attributes on the component tag
- No hardcoded attributes — all variant differences go into
args, not hardcoded in template strings
- Story names:
'🧩 <Name>' for web component, '🌍 <Name>' for HTML
For slots or complex content: If a variant needs different slot content, override withRender:
export const WithContent = Story({
args: {
variant: 'primary',
},
...withRender(
({ variant, ...args }) => `
<ds-<component> variant="${variant}" ${props(args)}>
Custom slot content here
</ds-<component>
`,
),
})
<component>.doc-config.ts
export const <COMPONENT>_DOC_CONFIG = {
section: 'Components / <ComponentName>',
color: 'purple' as const,
tabs: [
{ label: 'Overview', storyId: 'components-<component>--<component>' },
{ label: 'Usage', storyId: 'components-<component>--usage' },
{ label: 'Variants', storyId: 'components-<component>--variants-overview' },
{ label: 'Styling', storyId: 'components-<component>--styling' },
{ label: 'Accessibility', storyId: 'components-<component>--accessibility' },
{ label: 'Testing', storyId: 'components-<component>--testing' },
],
}
export const <COMPONENT>_TAB_TITLES = {
overview: 'Overview',
usage: 'Usage',
variants: 'Variants',
styling: 'Styling',
accessibility: 'Accessibility',
testing: 'Testing',
}
export const get<Component>Tabs = (activeLabel: keyof typeof <COMPONENT>_TAB_TITLES) => {
return <COMPONENT>_DOC_CONFIG.tabs.map(tab => ({
...tab,
active: tab.label === <COMPONENT>_TAB_TITLES[activeLabel],
}))
}
1-Overview.mdx
For Web Components only:
import { Controls, Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
CanvasWithCodePen,
ComponentLead,
ComponentPublicMethods,
Footer,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/<ComponentName>" />
<Banner label={'<ComponentName>'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('overview')} />
<ComponentLead component="<component>" />
<CanvasWithCodePen of={<Component>Stories.Basic} sourceState="shown" />
<Controls of={<Component>Stories.Basic} />
<ComponentPublicMethods component="<component>" />
<Footer />
For Hybrid or CSS-only:
import { Controls, Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
CanvasTabs,
ComponentLead,
ComponentPublicMethods,
Footer,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/<ComponentName>" />
<Banner label={'<ComponentName>'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('overview')} />
<ComponentLead component="<component>" />
<CanvasTabs htmlOf={<Component>Stories.BasicHtml} of={<Component>Stories.Basic} />
<Controls of={<Component>Stories.Basic} />
<ComponentPublicMethods component="<component>" />
<Footer />
2-Usage.mdx
import { Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
Code,
Footer,
UsageExamples,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Usage" />
<Banner label={'Usage'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('usage')} />
## When to Use
- [bullet list of valid use cases]
## When NOT to Use
- [bullet list of anti-patterns]
## Do's and Don'ts
### [Guideline Title]
<UsageExamples
items={[
{
type: 'correct',
title: 'Correct: [Short Label]',
content: (
<ds-<component>>Correct example</ds-<component>>
),
description: '[Why this is correct]',
},
{
type: 'incorrect',
title: 'Incorrect: [Short Label]',
content: (
<ds-<component>>Incorrect example</ds-<component>>
),
description: '[Why this is wrong]',
},
]}
/>
<Footer />
3-Variants.mdx
For Web Components only:
import { Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
CanvasWithCodePen,
Footer,
StoryHeading,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Variants/Overview" />
<StoryHeading of={<Component>Stories.Basic} hidden></StoryHeading>
<Banner label={'Variants'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('variants')} />
{/* STORIES */}
{/* ------------------------------------------------------ */}
<StoryHeading of={<Component>Stories.<StoryName>}></StoryHeading>
[One or two sentence description of this variant.]
<CanvasWithCodePen of={<Component>Stories.<StoryName>} sourceState="shown" />
{/* ------------------------------------------------------ */}
<StoryHeading of={<Component>Stories.<NextStoryName>}></StoryHeading>
[Description.]
<CanvasWithCodePen of={<Component>Stories.<NextStoryName>} sourceState="shown" />
<Footer />
For Hybrid or CSS-only:
import { Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
CanvasTabs,
Footer,
StoryHeading,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Variants/Overview" />
<StoryHeading of={<Component>Stories.Basic} hidden></StoryHeading>
<Banner label={'Variants'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('variants')} />
{/* STORIES */}
{/* ------------------------------------------------------ */}
<StoryHeading of={<Component>Stories.<StoryName>}></StoryHeading>
[One or two sentence description of this variant.]
<CanvasTabs htmlOf={<Component>Stories.<StoryName>Html} of={<Component>Stories.<StoryName>} />
{/* ------------------------------------------------------ */}
<StoryHeading of={<Component>Stories.<NextStoryName>}></StoryHeading>
[Description.]
<CanvasTabs htmlOf={<Component>Stories.<NextStoryName>Html} of={<Component>Stories.<NextStoryName>} />
<Footer />
Notes:
- The first
<StoryHeading hidden> above the Banner is required by Storybook routing — it registers the Basic story as the "variants" landing page.
- For hybrid/CSS-only components: each variant block pairs
<StoryName> with <StoryName>Html.
- For WC-only components: use
CanvasWithCodePen without HTML tabs.
4-Styling.mdx
import { Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
ComponentCssVariables,
ComponentDesignTokens,
ComponentParts,
Footer,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Styling" />
<Banner label={'Styling'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('styling')} />
<ComponentParts component="<component>" />
<ComponentCssVariables component="<component>" />
<ComponentDesignTokens component="<component>" />
<Footer />
Notes:
- Use
ComponentCssVariables and ComponentDesignTokens — NOT the old TokenOverview.
ComponentParts renders Shadow DOM parts from JSDoc @part tags automatically.
5-Accessibility.mdx
import { Canvas, Markdown, Meta } from '@storybook/addon-docs/blocks'
import {
A11yGuidelines,
Banner,
BannerTabs,
CanvasTabs,
Footer,
InfoQuote,
Lead,
StoryHeading,
TokenOverview,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Accessibility" />
<Banner label={'Accessibility'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('accessibility')} />
## Guidelines
<A11yGuidelines
items={[
{
type: 'do',
title: '[Guideline Title — actionable requirement]',
content: (
<p style={{ margin: '0.5rem 0 0 0' }}>
[Clear instruction for component consumers. Reference HTML attributes like <code>aria-label</code>,{' '}
<code>aria-describedby</code>, or semantic elements where relevant.]
</p>
),
},
{
type: 'do',
title: '[Second do example]',
content: <p style={{ margin: '0.5rem 0 0 0' }}>[Another important practice for this component.]</p>,
},
{
type: 'dont',
title: '[Anti-pattern Title — what to avoid]',
content: (
<p style={{ margin: '0.5rem 0 0 0' }}>
[What not to do and why it fails accessibility. Explain the consequence for users.]
</p>
),
},
{
type: 'dont',
title: '[Second dont example]',
content: <p style={{ margin: '0.5rem 0 0 0' }}>[Another common mistake that breaks accessibility.]</p>,
},
]}
/>
## References
- [MDN Reference 1](https://example.com) - [Brief description of what users will learn]
- [MDN Reference 2](https://example.com) - [How it relates to this component]
- [WCAG 2.2 Guideline](https://www.w3.org/WAI/WCAG22/) - [Which success criterion applies]
<Footer />
Accessibility content principle:
- Document consumer requirements only — not internal implementation.
- Include 4–6 do/don't guidelines (2–3 dos, 2–3 don'ts) that are component-specific and actionable.
- Add MDN and WCAG references at the end to direct users to authoritative resources.
- Guidelines should match the component's actual usage patterns. For example:
- Form fields (Select, Input, Checkbox): Focus on labels,
aria-describedby for error messages, required indication.
- Interactive buttons/links: Focus on text labels, focus visible, keyboard activation.
- Content containers (Badge, Card): Focus on semantic structure, color contrast, descriptive context.
- Navigation: Focus on landmarks, skip links, ARIA roles.
- Avoid generic guidelines; tailor each item to the component's role and user interactions.
Reusable Block Components
| Block | Import | Props | Purpose |
|---|
Banner | ../../../.storybook/blocks | label, section, color | Page header with title and accent color |
BannerTabs | ../../../.storybook/blocks | of, tabs | Navigation between Overview/Usage/etc |
CanvasTabs | ../../../.storybook/blocks | of, htmlOf? | Canvas with web component + HTML/CSS tabs (hybrid/CSS-only) |
CanvasWithCodePen | ../../../.storybook/blocks | of, sourceState? | Canvas with CodePen embed for web components (WC only) |
ComponentLead | ../../../.storybook/blocks | component | Auto-pulls description from components.json |
ComponentPublicMethods | ../../../.storybook/blocks | component, subComponents?, title? | Auto-pulls methods from components.json |
ComponentParts | ../../../.storybook/blocks | component | Auto-pulls Shadow DOM parts from components.json |
ComponentCssVariables | ../../../.storybook/blocks | component | CSS custom properties table |
ComponentDesignTokens | ../../../.storybook/blocks | component | Design tokens table |
A11yGuidelines | ../../../.storybook/blocks | items: { type, title, content }[] | Do/Don't accessibility checklist |
UsageExamples | ../../../.storybook/blocks | items: { type, title, content, description }[] | Correct/Incorrect code pattern comparison |
StoryHeading | ../../../.storybook/blocks | of, hidden? | Section title before canvas component |
Footer | ../../../.storybook/blocks | none | End-of-page footer |
ComponentPageObject | ../../../.storybook/blocks | component | PO API table + example test + install guide |
Code | ../../../.storybook/blocks | code, noPreview? | Inline code snippet in UsageExamples |
Shared Patterns
Meta title must be a string literal
Storybook's static indexer cannot evaluate expressions:
{/* ✓ correct */}
<Meta title="Components/Tag/Usage" />
{/* ✗ wrong — causes indexing error */}
<Meta title={`${TAG_DOC_CONFIG.section}/Usage`} />
Import only what you use
Each MDX page imports only the blocks it actually renders.
Section dividers in 3-Variants.mdx
Use {/* ------------------------------------------------------ */} before every StoryHeading.
Key story utilities
createCssMappings(tag) — maps component props to CSS class names for HTML variants
cssClasses(mappings, args, baseClass) — applies mapped CSS classes to an element
props(args) — serialises Stencil props to HTML attribute string
withRender(fn) — render function override
withComponentControls({ tag }) — argTypes from component JSDoc
StoryFactory<Args>(meta) — returns the Story() helper
6-Testing.mdx
import { Meta } from '@storybook/addon-docs/blocks'
import {
Banner,
BannerTabs,
ComponentPageObject,
Footer,
} from '../../../.storybook/blocks'
import * as <Component>Stories from './<component>.stories'
import { <COMPONENT>_DOC_CONFIG, get<Component>Tabs } from './<component>.doc-config'
<Meta title="Components/<ComponentName>/Testing" />
<Banner label={'Testing'} section={<COMPONENT>_DOC_CONFIG.section} color={<COMPONENT>_DOC_CONFIG.color} />
<BannerTabs of={<Component>Stories} tabs={get<Component>Tabs('testing')} />
<ComponentPageObject component="<component>" />
<Footer />
Notes:
- Keep this page minimal — no extra content beyond the components above. Do not add additional sections, guidelines, or explanatory text.
ComponentPageObject reads pageObject from components.json (populated during pnpm build).
- If the component has no
.po.ts in packages/playwright, the block shows "No page object available" + the install guide.
- If a page object exists, it renders Locators / Actions / Assertions tables, a semi-dynamic example test, and the install guide.
- The structure is always: Meta → Banner → BannerTabs → ComponentPageObject → Footer. Nothing more.
Reference: Tag Component
The tag is the canonical example for the current pattern:
docs/src/components/tag/
tag.stories.ts ← title: 'Components/Tag/Variants', paired 🧩/🌍 stories
tag.doc-config.ts ← TAG_DOC_CONFIG (color: 'purple'), getTagTabs()
1-Overview.mdx ← CanvasTabs + Controls + ComponentLead + ComponentPublicMethods
2-Usage.mdx ← UsageExamples block
3-Variants.mdx ← Meta title ends in /Variants/Overview, StoryHeading + CanvasTabs pairs
4-Styling.mdx ← ComponentParts + ComponentCssVariables + ComponentDesignTokens
5-Accessibility.mdx ← A11yGuidelines + WCAG compliance list
api.md ← AUTO-GENERATED — never edit