Storybook
Include stories, emit every variant you click, and keep production CSS lean.
Storybook for a design system is a Panda project that renders your package. Production may or may not run Panda. Stories always do, if they call recipes.
This page covers the package's own stories. To add Panda to an app that already has Storybook, use Using Storybook.
Include the story files
Panda only emits what it can see. Put stories on include.
packages/ds/panda.config.ts
export default defineConfig({
include: [
'src/**/*.{ts,tsx}',
'.storybook/**/*.{ts,tsx}',
'src/**/*.stories.tsx'
]
})A literal in a story is enough:
src/button/button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './button'
const meta: Meta<typeof Button> = { component: Button }
export default meta
export const Large: StoryObj<typeof Button> = {
args: { size: 'lg', children: 'Save' }
}<Button size="lg">Save</Button>Both extract. size={size} from a knob does not. See
Dynamic variants.
Emit every recipe variant
If stories pass variants from controls, pre-generate them. Fine in Storybook. Heavy in a production app.
packages/ds/panda.config.ts
export default defineConfig({
include: ['src/**/*.{ts,tsx}', 'src/**/*.stories.tsx'],
staticCss: {
recipes: '*'
}
})Or only the recipes you document:
packages/ds/panda.config.ts
export default defineConfig({
staticCss: {
recipes: {
button: ['*'],
card: ['*']
}
}
})Do not copy recipes: '*' into the production app's config. That is the usual "Storybook looks complete, the app
CSS is huge" mistake.
Point Storybook at the design system
When stories live in an app that consumes the package:
apps/docs/panda.config.ts
export default defineConfig({
designSystem: '@acme/ds',
include: ['src/**/*.{ts,tsx}', '.storybook/**/*.{ts,tsx}'],
staticCss: {
recipes: '*'
}
})When stories live in the library, designSystem is not set. The library is the design system. Run
panda codegen and panda lib --watch (or cssgen) next to Storybook.
Why the app looks blank
// story: literal. CSS emits.
export const Large = { args: { size: 'lg' } }
// app: a prop. extract sees nothing.
<Button size={props.size}>Save</Button>Storybook had a literal. The app passed a variable. Fix it in the app with a literal, or with a narrow
staticCss rule. Do not "fix" it by shipping every variant to production.
Import the CSS
.storybook/preview.ts
import '../styled-system/styles.css'
import type { Preview } from '@storybook/react'
const preview: Preview = {}
export default previewIf production is Consume without Panda, Storybook can import the same
dist/styles.css you publish. The controls path still needs staticCss or literals, because that file is frozen
at cssgen time.
See also
- Using Storybook for PostCSS and Vite setup
- Static CSS
- Dynamic variants
- Monorepo workflow