Skip to content
HELiX

Storybook Story Standards

apps/docs/src/content/docs/guides/storybook-standards Click to copy
Copied! apps/docs/src/content/docs/guides/storybook-standards

Every public HELiX custom element ships with a colocated .stories.ts file. (Internal child elements that exist purely to be slotted under their parent — e.g. hx-accordion-item or hx-radio — may not have their own story file; the parent’s stories exercise them. The story-coverage audit excludes those by directory.) These stories serve as living documentation, interactive examples, and visual regression test fixtures. This guide documents the standards every story file must meet.

src/components/hx-button/
hx-button.ts # Component source
hx-button.styles.ts # Styles
hx-button.test.ts # Tests
hx-button.stories.ts # Stories (this file)

Every component story file must include:

A Default export showing the component with minimal props (with rare exceptions like hx-theme, which is a configuration-only element with no visible default state). This serves as the primary documentation entry point.

// hx-button's default slot is the label — pass it via render, not an args 'label' prop.
export const Default: Story = {
render: () => html`<hx-button>Button</hx-button>`,
};

One story per visual variant (variant, hx-size, etc.). Name stories after the variant value. HELiX components don’t use an appearance property — drop that if it appears in legacy stories.

export const Primary: Story = { args: { variant: 'primary' } };
export const Secondary: Story = { args: { variant: 'secondary' } };
export const Danger: Story = { args: { variant: 'danger' } };

Stories for interactive states: disabled, loading, error, readonly.

export const Disabled: Story = { args: { disabled: true } };
export const WithError: Story = { args: { error: 'This field is required' } };

Stories demonstrating named slots when the component uses them.

export const WithIcon: Story = {
render: () => html`
<hx-button>
<hx-icon slot="prefix" library="default" name="check"></hx-icon>
Save
</hx-button>
`,
};

hx-icon requires an explicit library attribute to resolve from a registered icon library (e.g. default, helix); without it the component falls back to document-local sprite lookup.

Storybook 10.x reads the Custom Elements Manifest (custom-elements.json) to auto-generate:

  • Property tables with types, defaults, and descriptions
  • Event documentation
  • CSS custom property tables
  • Slot documentation
  • CSS part documentation

Ensure all public API surfaces have JSDoc in the component source. The CEM analyzer extracts documentation from:

  • @property() decorator descriptions
  • @fires JSDoc tags
  • @slot JSDoc tags
  • @csspart JSDoc tags
  • @cssprop JSDoc tags

Use the meta object to configure story behavior:

const meta: Meta = {
title: 'Components/Button',
component: 'hx-button',
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary', 'danger', 'ghost', 'outline'],
},
},
};
export default meta;

Storybook titles use a humanized name (Components/Button), not the tag name. Keep the component field as the literal tag name so Storybook can wire CEM-driven autodocs. The variant options list must match the component’s CEM union exactly — for hx-button that’s 'primary' | 'secondary' | 'tertiary' | 'danger' | 'ghost' | 'outline'.

Components with complex ARIA patterns should include stories that demonstrate accessibility behavior:

/** Demonstrates keyboard navigation through the listbox. */
export const KeyboardNavigation: Story = { ... };
/** Screen reader announcement when error state is set. */
export const ErrorAnnouncement: Story = { ... };

For clinical components (hx-phi-field, hx-patient-banner, hx-clinical-status), include stories that demonstrate the healthcare-specific behavior:

/** PHI field with masked SSN value. */
export const MaskedSSN: Story = { ... };
/** Patient banner with two-identifier rule enforcement. */
export const TwoIdentifierRule: Story = { ... };

Run the story coverage audit to verify component directories have a .stories.ts file:

Terminal window
node scripts/audit-story-coverage.js

The audit walks packages/hx-library/src/components/*/ and reports any directory missing a .stories.ts file. The check is local-only today — it is not wired into the GitHub Actions CI pipeline. Run it manually before opening a PR that adds or restructures component directories.