name: f0-storybook-stories description: Create Storybook story files (.stories.tsx) for F0 React components. Use when adding or updating stories, snapshot stories, argTypes, decorators, play functions, or internal stories. For MDX documentation files, use the global factorial-f0-component-documentation skill instead.
F0 Storybook Stories
Creates .stories.tsx files for F0 components. For MDX docs (the Docs tab), use the global factorial-f0-component-documentation skill instead — that's a separate file.
Quick Reference
| Need | See |
|---|---|
| Meta definition, tags, argTypes, Snapshot, decorators, play functions | references/story-patterns.md |
| Play functions, portal queries, step(), a11y configuration | Load the f0-storybook-testing skill |
Key Rules
Imports
import type { Meta, StoryObj } from "@storybook/react-vite"
import { fn, expect, within } from "storybook/test"
import { withSnapshot, withSkipA11y } from "@/lib/storybook-utils/parameters"
// Icons: "@/icons/app" | "@/icons/modules" | "@/icons/ai"
Meta
Always satisfies Meta<typeof Component> (not as Meta). Exception: components with complex generics use plain Meta.
const meta = {
title: "ComponentName",
component: F0Example,
tags: ["stable", "!autodocs"], // or "experimental" | "internal" | "deprecated". Always include "!autodocs" when a manual MDX page exists.
// ...
} satisfies Meta<typeof F0Example>
export default meta
type Story = StoryObj<typeof meta>
Snapshots are OFF by default
Why: Chromatic charges per snapshot and visual-diff reviews get noisy when the same component produces many captures. The policy is one consolidated snapshot per component — a single image that shows every meaningful variant in a flex layout — so a visual change produces exactly one diff to review.
Rule: every component has exactly one story exported as Snapshot
that opts in with withSnapshot({}). Do not scatter withSnapshot()
across multiple stories; consolidate all variants into the Snapshot story.
export const Snapshot: Story = {
parameters: withSnapshot({}), // opt in
}
// With a11y skip: withSkipA11y(withSnapshot({}))
Tags
| Tag | When | Sidebar |
|---|---|---|
"stable" |
Production-ready | ✅ toolbar only |
"experimental" |
Still stabilizing | 🚧 sidebar + toolbar |
"internal" |
Not public | 🔒 sidebar + toolbar |
"deprecated" |
Avoid using | ⛔ sidebar + toolbar |
"!autodocs" |
Manual MDX page exists | Prevents duplicate autogenerated Docs page |
"no-sidebar" |
Hide story from sidebar | Hidden from Storybook navigation. Never apply to the first exported (primary) story when an attached MDX exists — it propagates to the --documentation entry and hides the whole Documentation page. |
internalis not a maturity level. Use it for components insrc/ui/(Radix wrappers) and for stories you want hidden from the public Storybook sidebar but visible locally. The three F0 maturity levels arestable,experimental, anddeprecated— see Component Maturity.
Promotion and deprecation: when a component is promoted from
experimental/to stable, change the tag from"experimental"to"stable". When deprecating, change to"deprecated"and add@deprecated+@removeIn+@migrationJSDoc tags on the export. See thef0-component-promotionskill and the Deprecation & Removal policy.
Note: Storybook applies
tags: ["autodocs"]globally via.storybook/preview.tsx. To opt out of autodocs when a manual MDX page exists, add"!autodocs"to the meta tags. Removing"autodocs"alone is not enough.
Manual MDX requires a visible first story
When a component has a manual MDX file (F0Example.mdx with <Meta of={Stories} />), Storybook attaches the MDX docs entry to the first exported story and inherits its tags onto the docs entry.
Consequence: if the first exported story has tags: ["no-sidebar"], the Documentation page also gets no-sidebar and disappears from the sidebar — the MDX is still served but unreachable through navigation.
Rule: with manual MDX, the first exported story must NOT include no-sidebar. Conventionally that first story is Default, and it must remain visible in the sidebar. Hidden helper stories (no-sidebar) referenced by <Canvas of={...} /> come after.
ArgTypes for union props
Reference the component's exported const array — don't hardcode the options:
import { buttonSizes, buttonVariants } from "../F0Example"
argTypes: {
size: {
control: "select",
options: buttonSizes,
table: { type: { summary: buttonSizes.join(" | ") } },
},
}
Render-only stories must include args
Stories that use a custom render function without inheriting args from the meta
must include an explicit args property to satisfy the Story type constraint.
Omitting args causes a TypeScript error when the meta has required props.
// WRONG — TS error: Property 'args' is missing
export const Variants: Story = {
tags: ["!dev"],
render: () => <div>...</div>,
}
// CORRECT — provide required args even if render ignores them
export const Variants: Story = {
tags: ["!dev"],
args: { items: [], onClick: () => {} }, // satisfy required props from Meta
render: () => <div>...</div>,
}