# Blocks — Stories Guide

Canonical reference for writing `.stories.tsx` files and JSDoc in `libs/blocks`.

This is the package-facing runtime guide for Storybook stories in this package.

For runtime routing and source precedence, start with `AGENTS.md`.

The maintainer skill at `skills/blocks-stories/SKILL.md` is **workflow only**. If anything in that skill (or in chat) disagrees with this guide, **this file wins**.

---

## Meta, typing, and subcomponents

Prefer a named `meta` object and derive the story type from it:

```tsx
const meta = {
  component: Button,
  title: 'Blocks/Button',
} satisfies Meta<typeof Button>

export default meta

type Story = StoryObj<typeof meta>
```

If `satisfies Meta<typeof Component>` leaks private third-party types, use an
explicit annotation instead:

```tsx
const meta: Meta<typeof Select> = {
  component: Select,
  title: 'Form/Select/Primitive',
}
```

For composed components (Modal, Select, DropdownMenu, Tooltip, etc.), add
`subcomponents` to `meta` so Storybook exposes subcomponent tabs for reference.

List every exported subcomponent consumers will use. Omit internal-only
implementation primitives.

Those tabs are reference surfaces, not standalone story surfaces. Do not create
separate story files just to force subcomponent prop tables. Prefer JSDoc on
exported subcomponent props and consts, `meta.subcomponents` for discoverability,
and short MDX prose when Storybook collapses an option type to something broad
like `union`.

---

## JSDoc

### Component-level

Every exported component should have a one-sentence JSDoc on the export. That
is what Storybook MCP tends to surface first.

### Prop-level

Public props should carry semantic descriptions on the TypeScript interface.
That is the single source of truth for Controls and for readers of source.

---

## argTypes and MDX-bound Controls

Storybook reads prop descriptions from JSDoc. Do **not** add `description` in
`argTypes`; it duplicates JSDoc and creates a maintenance split.

Use `argTypes` only for `control` and `options` that Storybook cannot infer:

```tsx
import { BUTTON_VARIANT_OPTIONS, BUTTON_SIZE_OPTIONS } from './Button'

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: 'select',
      options: BUTTON_VARIANT_OPTIONS,
    },
    size: {
      control: 'select',
      options: BUTTON_SIZE_OPTIONS,
    },
    onClick: { control: false },
    onOpenChange: { control: false },
  },
} satisfies Meta<typeof Button>
```

Typical uses:

- string unions that need a select control
- callback props hidden from Controls
- booleans where the default state is non-obvious

Skip `argTypes` when inference is already correct. For finite design-system
values, derive `options` from exported component constants—never story-local
copies of component-owned value sets.

When MDX uses `<Controls of={Stories.Default} />`, that story must still expose
real controllable args. Disabling every root prop in `argTypes` makes the
Controls block useless.

---

## Story naming and scope

Each story should demonstrate one thing: one variant, one state, one composition
pattern, or one layout behavior.

Good: `Primary`, `Disabled`, `WithIcon`, `SmallSize`, `LoadingState`.

Bad: `SizesAndVariants`, `AllExamples`, `KitchenSink`.

Exception: `All<PropName>` galleries are fine when they vary a single prop
across values (`AllSides`, `AllVariants`).

---

## Tags

```tsx
tags: ['!manifest'] // exclude from AI manifest, keep visible in sidebar
tags: ['!dev'] // exclude from sidebar, keep in AI manifest
tags: ['!test'] // exclude from test runner
tags: ['!dev', '!test'] // manifest-only
```

Use `!manifest` for anti-pattern demos, deprecated stories, and internal
scaffolding that should not be used as AI examples.

---

## Docs tab, MCP, and the source view

MCP heavily surfaces the first three stories for a component—treat them as copy
targets. Keep `Default`, size, state, and composition stories explicit; avoid
story inheritance for those; avoid hidden helpers or data that make the snippet
impossible to copy. Keep `render` explicit even when unused:
`render: (_args) => (...)`.

Add `parameters.docs.description.story` when the purpose of a story is
non-obvious (when and why to use the pattern, not what it renders). Skip it for
self-explanatory names like `Default` or `Primary`.

Write meaningful JSX inline inside `render`. Do not hide composition behind
opaque helpers like `render: () => <ComponentDemo />`, or the source tab shows
the wrapper instead of the useful example. Stateful stories may use a small
local component when hooks are required, but the visible source should still
reflect the real composition as much as possible.

Do not paper over source problems with `parameters.docs.source.code` unless
there is no better option—hand-written snippets drift from the real story.

---

## Triggered components (open by default in stories)

For open/closed surfaces (Tooltip, Modal, Popover, Select), use conditional
`defaultOpen` when you need story-mode screenshots open without forcing every MDX
`<Canvas>` to render open.

Prefer `defaultOpen` on uncontrolled roots when available. Use controlled
`open` only when the component or story genuinely needs it.
