--- name: panda-create-stories description: Create Storybook stories that document and demonstrate Panda CSS components with variants, accessibility testing, and interactive controls --- # Panda CSS Component Stories ## When to Use This Skill Use this skill when: - Creating Storybook documentation for Panda CSS components - Demonstrating component variants and props - Building a component library with visual documentation - Testing component accessibility interactively - Showcasing responsive behavior and theme switching - Creating a design system playground - Onboarding developers to component APIs This skill assumes you have: - An existing component (use **panda-component-impl** skill to create one) - Storybook installed and configured in your project - Basic understanding of component props and variants ## Storybook Setup Prerequisites ### Quick Storybook Check Before creating stories, verify Storybook is installed: ```bash # Check for Storybook dependencies ls -la .storybook/ # Look for Storybook scripts in package.json cat package.json | grep storybook ``` ### If Storybook Not Installed Install Storybook with modern defaults: ```bash npx storybook@latest init ``` **Recommended addons for Panda CSS projects**: ```bash npm install --save-dev @storybook/addon-a11y @storybook/addon-themes @storybook/test ``` ### Storybook Configuration for Panda CSS Ensure `.storybook/main.ts` includes your Panda CSS output: ```typescript import type { StorybookConfig } from '@storybook/react-vite' const config: StorybookConfig = { stories: ['../src/**/*.stories.@(ts|tsx)'], addons: [ '@storybook/addon-essentials', '@storybook/addon-a11y', // Accessibility testing '@storybook/addon-themes', // Theme switching '@storybook/addon-interactions', // Interactive testing ], framework: { name: '@storybook/react-vite', options: {}, }, } export default config ``` Configure theme switching in `.storybook/preview.tsx`: ```typescript import type { Preview } from '@storybook/react' import { withThemeByClassName } from '@storybook/addon-themes' // Import Panda CSS output import '../styled-system/styles.css' const preview: Preview = { decorators: [ withThemeByClassName({ themes: { light: '', // No class for light (default) dark: 'dark', // Add 'dark' class for dark mode }, defaultTheme: 'light', }), ], parameters: { controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, }, } export default preview ``` ## Story File Structure ### File Naming Convention Stories are **colocated** with components: ``` src/ components/ Button/ Button.tsx # Component implementation Button.stories.tsx # Stories file index.tsx # Public exports CheckBox/ CheckBox.tsx CheckBox.stories.tsx index.tsx ``` **Pattern**: `ComponentName.stories.tsx` in same directory as component. ### Basic Story Structure (CSF3 Format) CSF3 (Component Story Format 3) is the modern, concise format. Create: `src/components/Button/Button.stories.tsx` ```typescript import type { Meta, StoryObj } from '@storybook/react' import { Button } from './Button' // Meta defines default component configuration const meta = { title: 'Components/Button', component: Button, parameters: { layout: 'centered', }, tags: ['autodocs'], argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'], description: 'Visual style variant', }, size: { control: 'radio', options: ['small', 'medium', 'large'], description: 'Size variant', }, disabled: { control: 'boolean', description: 'Disable the button', }, loading: { control: 'boolean', description: 'Show loading state', }, }, } satisfies Meta export default meta type Story = StoryObj // Default story - most common usage export const Primary: Story = { args: { children: 'Button', variant: 'primary', size: 'medium', }, } // Additional variant stories export const Secondary: Story = { args: { children: 'Button', variant: 'secondary', size: 'medium', }, } export const Outline: Story = { args: { children: 'Button', variant: 'outline', size: 'medium', }, } export const Ghost: Story = { args: { children: 'Button', variant: 'ghost', size: 'medium', }, } // Size variants export const Small: Story = { args: { children: 'Button', variant: 'primary', size: 'small', }, } export const Large: Story = { args: { children: 'Button', variant: 'primary', size: 'large', }, } // State stories export const Disabled: Story = { args: { children: 'Button', variant: 'primary', disabled: true, }, } export const Loading: Story = { args: { children: 'Button', variant: 'primary', loading: true, }, } // With icons (if component supports) export const WithLeftIcon: Story = { args: { children: 'Button', variant: 'primary', leftIcon: '←', }, } ``` ## Story Creation Process with TodoWrite When creating stories for a component, use TodoWrite to track systematic coverage: ### Create Story Creation Checklist ```typescript TodoWrite({ todos: [ { content: "Read component file and inspect actual props", status: "pending", activeForm: "Reading component file and inspecting actual props" }, { content: "Create story file with meta configuration", status: "pending", activeForm: "Creating story file with meta configuration" }, { content: "Document props with argTypes (only actual component props)", status: "pending", activeForm: "Documenting props with argTypes" }, { content: "Add Default story showing primary usage", status: "pending", activeForm: "Adding Default story showing primary usage" }, { content: "Add All States story showing all variants/states together", status: "pending", activeForm: "Adding All States story showing all variants/states together" }, { content: "Add example stories with 'Ex:' prefix (interactive, patterns)", status: "pending", activeForm: "Adding example stories with 'Ex:' prefix" }, { content: "Add accessibility stories with 'A11y:' prefix (keyboard, focus)", status: "pending", activeForm: "Adding accessibility stories with 'A11y:' prefix" }, { content: "Test stories in Storybook UI", status: "pending", activeForm: "Testing stories in Storybook UI" }, ] }) ``` ### CRITICAL: Inspect Component Props First **Before creating argTypes**, read the component file to understand its actual props: ```typescript // Read the component file const componentCode = await Read({ file_path: 'src/components/Button/Button.tsx' }) // Identify the prop type definition // Example: export type ButtonProps = BoxProps & ButtonVariantProps & { ... } // Only include props that are: // 1. Explicitly defined in the component's prop type // 2. Relevant for user control (not internal implementation details) // 3. Actually used by the component // DO NOT include props from base components (like 'as' from Box) unless the component explicitly exposes them ``` ## Meta Configuration Patterns ### Basic Meta ```typescript const meta = { title: 'Components/Button', // Sidebar organization component: Button, // Component reference tags: ['autodocs'], // Generate docs automatically } satisfies Meta ``` ### Meta with Parameters ```typescript const meta = { title: 'Components/Button', component: Button, parameters: { layout: 'centered', // Center in canvas docs: { description: { component: 'A versatile button component with multiple variants and states.', }, }, }, tags: ['autodocs'], } satisfies Meta ``` ### Meta with ArgTypes (Control Panel) ```typescript const meta = { title: 'Components/Button', component: Button, tags: ['autodocs'], argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'], description: 'Visual style variant', table: { type: { summary: 'string' }, defaultValue: { summary: 'primary' }, }, }, size: { control: 'radio', options: ['small', 'medium', 'large'], description: 'Size variant', }, onClick: { action: 'clicked', // Log to Actions panel }, disabled: { control: 'boolean', }, // Hide props that shouldn't be controlled className: { table: { disable: true }, }, }, } satisfies Meta ``` ## Story Patterns for Panda CSS Components ### Recommended Story Structure **Prefer this structure** for better Storybook organization: 1. **Default** - Most common usage (1 story) 2. **All States** - All variants/states shown together (1 story) 3. **Ex: [Pattern Name]** - Example patterns and use cases (multiple stories) 4. **A11y: [Test Name]** - Accessibility testing stories (multiple stories) **Why this approach?** - **Reduces clutter**: One "All States" story instead of 6+ individual state stories - **Better scanning**: Prefixes group related stories together in sidebar - **Easier comparison**: See all states side-by-side - **Clearer purpose**: "Ex:" for examples, "A11y:" for accessibility tests ### Pattern 1: All States Story (Preferred) Show all variant combinations and states in one comprehensive view: ```typescript export const AllVariants: Story = { render: () => ( {/* Primary variants */} {/* Secondary variants */} {/* Outline variants */} {/* Ghost variants */} ), } ``` ### Pattern 2: Example Stories (Use "Ex:" Prefix) Show interactive patterns and real-world use cases. Use "Ex:" prefix in the story name for better organization: ```typescript // Note: Story names in sidebar will show as "Ex: Interactive Toggle" export const ExInteractiveToggle: Story = { name: 'Ex: Interactive Toggle', render: () => { const [isOn, setIsOn] = useState(false) return ( ) }, } export const ExFormIntegration: Story = { name: 'Ex: Form Integration', render: () => (
{ e.preventDefault(); alert('Submitted!') }}>
), } export const ExLoadingSimulation: Story = { name: 'Ex: Loading Simulation', render: () => { const [loading, setLoading] = useState(false) const handleClick = () => { setLoading(true) setTimeout(() => setLoading(false), 2000) } return ( ) }, } ``` **Why "Ex:" prefix?** - Groups all example stories together in Storybook sidebar - Clearly indicates these are usage examples, not baseline states - Easier to scan and find relevant patterns ### Pattern 3: Theme Comparison Show light and dark themes side by side: ```typescript export const ThemeComparison: Story = { render: () => ( {/* Light theme */} Light Theme {/* Dark theme */} Dark Theme ), } ``` ### Pattern 4: Responsive Behavior Show responsive props: ```typescript export const Responsive: Story = { render: () => ( ), parameters: { viewport: { defaultViewport: 'mobile1', }, }, } ``` ## Accessibility Stories with Play Functions (Use "A11y:" Prefix) Play functions enable automated accessibility testing. Use "A11y:" prefix for these stories to group them together in the sidebar. ### Basic Click Interaction ```typescript import { userEvent, within, expect } from '@storybook/test' export const A11yClickInteraction: Story = { name: 'A11y: Click Interaction', args: { children: 'Click Me', variant: 'primary', }, play: async ({ canvasElement }) => { const canvas = within(canvasElement) // Find the button const button = canvas.getByRole('button', { name: /click me/i }) // Click it await userEvent.click(button) // Verify it's clickable (no errors thrown) }, } ``` ### Keyboard Navigation Test ```typescript export const A11yKeyboardNavigation: Story = { name: 'A11y: Keyboard Navigation', render: () => ( ), play: async ({ canvasElement }) => { const canvas = within(canvasElement) const firstButton = canvas.getByRole('button', { name: /first/i }) // Focus first button firstButton.focus() // Tab to next button await userEvent.tab() // Verify second button is focused const secondButton = canvas.getByRole('button', { name: /second/i }) expect(secondButton).toHaveFocus() }, } ``` ### Form Interaction Test ```typescript export const A11yFormSubmission: Story = { name: 'A11y: Form Submission', render: () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() console.log('Form submitted') } return (
) }, play: async ({ canvasElement }) => { const canvas = within(canvasElement) // Type in input const input = canvas.getByPlaceholderText('Name') await userEvent.type(input, 'John Doe') // Click submit const submitButton = canvas.getByRole('button', { name: /submit/i }) await userEvent.click(submitButton) }, } ``` ### Accessibility Validation ```typescript import { expect } from '@storybook/test' export const A11yAccessibilityCheck: Story = { name: 'A11y: Accessibility Check', args: { children: 'Accessible Button', variant: 'primary', }, play: async ({ canvasElement }) => { const canvas = within(canvasElement) const button = canvas.getByRole('button') // Verify button has accessible name expect(button).toHaveAccessibleName() // Verify focus visible on keyboard interaction button.focus() expect(button).toHaveFocus() // Verify disabled buttons are not clickable }, parameters: { a11y: { config: { rules: [ { id: 'color-contrast', enabled: true, }, ], }, }, }, } ``` ## Slot Recipe Component Stories Multi-part components require special story patterns. ### CheckBox Stories Example Create: `src/components/CheckBox/CheckBox.stories.tsx` ```typescript import type { Meta, StoryObj } from '@storybook/react' import { CheckBox } from './CheckBox' const meta = { title: 'Components/CheckBox', component: CheckBox, parameters: { layout: 'centered', }, tags: ['autodocs'], argTypes: { size: { control: 'radio', options: ['small', 'medium', 'large'], }, checked: { control: 'boolean', }, disabled: { control: 'boolean', }, indeterminate: { control: 'boolean', }, error: { control: 'boolean', }, }, } satisfies Meta export default meta type Story = StoryObj export const Default: Story = { args: { label: 'Accept terms', }, } export const Checked: Story = { args: { label: 'Checked', checked: true, }, } export const Indeterminate: Story = { args: { label: 'Indeterminate', indeterminate: true, }, } export const Disabled: Story = { args: { label: 'Disabled', disabled: true, }, } export const Error: Story = { args: { label: 'Has error', error: true, }, } // Show all states together export const AllStates: Story = { render: () => ( ), } // Interactive checkbox group export const CheckboxGroup: Story = { render: () => { const [checked, setChecked] = React.useState({ option1: false, option2: true, option3: false, }) return ( setChecked({ ...checked, option1: e.target.checked })} /> setChecked({ ...checked, option2: e.target.checked })} /> setChecked({ ...checked, option3: e.target.checked })} /> ) }, } ``` ## ArgTypes Configuration Reference ### Control Types ```typescript argTypes: { // Select dropdown variant: { control: 'select', options: ['primary', 'secondary', 'outline'], }, // Radio buttons (for fewer options) size: { control: 'radio', options: ['small', 'medium', 'large'], }, // Text input label: { control: 'text', }, // Number input with range opacity: { control: { type: 'range', min: 0, max: 1, step: 0.1 }, }, // Boolean checkbox disabled: { control: 'boolean', }, // Color picker backgroundColor: { control: 'color', }, // Date picker publishDate: { control: 'date', }, // Object editor config: { control: 'object', }, // Action logger (for event handlers) onClick: { action: 'clicked', }, // Hide from controls className: { table: { disable: true }, }, } ``` ### ArgType Documentation ```typescript argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'], description: 'The visual style variant of the button', table: { type: { summary: 'string' }, defaultValue: { summary: 'primary' }, category: 'Appearance', }, }, size: { control: 'radio', options: ['small', 'medium', 'large'], description: 'Controls the size and padding of the button', table: { type: { summary: 'string' }, defaultValue: { summary: 'medium' }, category: 'Appearance', }, }, disabled: { control: 'boolean', description: 'Disables the button and prevents interaction', table: { type: { summary: 'boolean' }, defaultValue: { summary: 'false' }, category: 'State', }, }, } ``` ## Best Practices Checklist Create TodoWrite items when creating stories: - [ ] Read component file and inspect actual props before creating argTypes - [ ] Story file is colocated with component (same directory) - [ ] Using CSF3 format (modern, concise) - [ ] Default story shows most common usage - [ ] All States story shows all variants/states together (preferred over individual state stories) - [ ] ArgTypes document only actual component props (not inherited props like 'as' from Box) - [ ] Example stories use "Ex:" prefix for better sidebar organization - [ ] Accessibility stories use "A11y:" prefix for better sidebar organization - [ ] Interactive examples use play functions - [ ] Accessibility checked with @storybook/addon-a11y - [ ] Theme switching works (light/dark) if component is theme-aware - [ ] Responsive behavior demonstrated if applicable - [ ] Stories follow consistent naming convention - [ ] Stories don't include implementation details - [ ] Meta title follows hierarchy (Components/Category/Name) ## Common Story Patterns ### Composition Story Show how component composes with others: ```typescript export const ComposedWithIcon: Story = { render: () => ( ), } ``` ### Custom Styling Override Show how to override with Panda CSS props: ```typescript export const CustomStyling: Story = { render: () => ( ), } ``` ### Loading State Simulation Demonstrate async behavior: ```typescript export const LoadingSimulation: Story = { render: () => { const [loading, setLoading] = React.useState(false) const handleClick = () => { setLoading(true) setTimeout(() => setLoading(false), 2000) } return ( ) }, } ``` ## Story Organization ### Title Hierarchy Organize stories in logical groups: ```typescript // Button variations title: 'Components/Button' // Basic components title: 'Components/Inputs/TextInput' // Nested categories title: 'Patterns/Forms/LoginForm' // Pattern examples title: 'Layouts/Grid' // Layout components ``` ### Parameters for Layout ```typescript parameters: { layout: 'centered', // Center in canvas (default for small components) layout: 'fullscreen', // Full viewport (for pages/layouts) layout: 'padded', // Add padding around component } ``` ### Tags ```typescript tags: [ 'autodocs', // Generate documentation automatically 'dev', // Only show in dev environment 'test', // Mark as test story ] ``` ## Accessing Storybook Documentation For advanced patterns and latest best practices: ```typescript // Resolve Storybook library ID mcp__MCP_DOCKER__resolve-library-id({ libraryName: "storybook" }) // Fetch relevant documentation mcp__MCP_DOCKER__get-library-docs({ context7CompatibleLibraryID: "/storybookjs/storybook", topic: "writing-stories" // or "args", "play-function", "theming" }) ``` **Topics to reference**: - `writing-stories` - CSF3 format and story patterns - `args` - ArgTypes and controls - `play-function` - Interactive testing - `theming` - Theme switching and decoration ## Common Pitfalls ### Avoid: Complex Logic in Stories ```typescript // BAD: Business logic in story export const BadStory: Story = { render: () => { const data = fetchDataFromAPI() // Don't fetch real data const processed = complexProcessing(data) // Don't do heavy computation return }, } // GOOD: Simple, declarative stories export const GoodStory: Story = { args: { children: 'Button Text', variant: 'primary', }, } ``` ### Avoid: Testing Implementation Details ```typescript // BAD: Testing internal state export const BadTest: Story = { play: async ({ canvasElement }) => { const button = canvasElement.querySelector('.button-internal-class') expect(button.state.isActive).toBe(true) // Don't access internal state }, } // GOOD: Testing user-visible behavior export const GoodTest: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement) const button = canvas.getByRole('button') await userEvent.click(button) expect(button).toHaveAttribute('aria-pressed', 'true') }, } ``` ### Avoid: Not Using ArgTypes ```typescript // BAD: No controls const meta = { title: 'Components/Button', component: Button, } satisfies Meta // GOOD: Document with argTypes const meta = { title: 'Components/Button', component: Button, argTypes: { variant: { control: 'select', options: ['primary', 'secondary'], description: 'Visual style variant', }, }, } satisfies Meta ``` ## Integration with Panda CSS Workflows This skill works with: - **panda-component-impl**: Reference component patterns for story examples - **panda-recipe-patterns**: Use recipe variants in story demonstrations - **panda-review-component**: Stories help validate component accessibility ## Complete Example: Button Stories This example demonstrates all the recommended patterns: ```typescript import type { Meta, StoryObj } from '@storybook/react' import { useState } from 'react' import { userEvent, within, expect } from '@storybook/test' import { Button } from './Button' import { Box } from '../Box/Box' const meta = { title: 'Components/Button', component: Button, parameters: { layout: 'centered', docs: { description: { component: 'A versatile button component with multiple variants, sizes, and states. Built with Panda CSS recipes.', }, }, }, tags: ['autodocs'], argTypes: { // IMPORTANT: Only include props that are explicitly defined in ButtonProps // Do NOT include inherited props like 'as' from BoxProps unless Button explicitly exposes them variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'], description: 'Visual style variant', table: { defaultValue: { summary: 'primary' }, }, }, size: { control: 'radio', options: ['small', 'medium', 'large'], description: 'Size variant', table: { defaultValue: { summary: 'medium' }, }, }, disabled: { control: 'boolean', description: 'Disable the button', }, loading: { control: 'boolean', description: 'Show loading state', }, onClick: { action: 'clicked', }, }, } satisfies Meta export default meta type Story = StoryObj // 1. Default - Most common usage export const Default: Story = { args: { children: 'Button', variant: 'primary', }, } // 2. All States - Show everything together (PREFERRED over individual state stories) export const AllStates: Story = { name: 'All States', render: () => ( ), } // 3. Example Stories - Use "Ex:" prefix for patterns and use cases export const ExInteractiveToggle: Story = { name: 'Ex: Interactive Toggle', render: () => { const [isOn, setIsOn] = useState(false) return ( ) }, } export const ExLoadingSimulation: Story = { name: 'Ex: Loading Simulation', render: () => { const [loading, setLoading] = useState(false) const handleClick = () => { setLoading(true) setTimeout(() => setLoading(false), 2000) } return ( ) }, } export const ExFormIntegration: Story = { name: 'Ex: Form Integration', render: () => (
{ e.preventDefault(); alert('Submitted!') }}>
), } // 4. Accessibility Stories - Use "A11y:" prefix for accessibility tests export const A11yAccessibilityCheck: Story = { name: 'A11y: Accessibility Check', args: { children: 'Accessible Button', variant: 'primary', }, play: async ({ canvasElement }) => { const canvas = within(canvasElement) const button = canvas.getByRole('button') // Verify accessible name expect(button).toHaveAccessibleName('Accessible Button') // Test keyboard focus button.focus() expect(button).toHaveFocus() // Test click await userEvent.click(button) }, parameters: { a11y: { config: { rules: [ { id: 'color-contrast', enabled: true }, { id: 'button-name', enabled: true }, ], }, }, }, } export const A11yKeyboardNavigation: Story = { name: 'A11y: Keyboard Navigation', render: () => ( ), play: async ({ canvasElement }) => { const canvas = within(canvasElement) const firstButton = canvas.getByRole('button', { name: /first/i }) firstButton.focus() await userEvent.tab() const secondButton = canvas.getByRole('button', { name: /second/i }) expect(secondButton).toHaveFocus() }, } ``` **Story Organization in Storybook Sidebar:** - Default - All States - Ex: Form Integration - Ex: Interactive Toggle - Ex: Loading Simulation - A11y: Accessibility Check - A11y: Keyboard Navigation ## Skill Usage Summary When creating stories for a Panda CSS component: 1. **Read component file** - Inspect actual props and types before creating stories 2. **Verify Storybook setup** - Check installation and configuration 3. **Create story file** - Colocate with component: `ComponentName.stories.tsx` 4. **Define meta** - Configure title, component, argTypes (only actual component props) 5. **Create Default story** - Show most common usage 6. **Create All States story** - Show all variants/states together (preferred over individual stories) 7. **Add example stories** - Use "Ex:" prefix for patterns and use cases 8. **Add accessibility stories** - Use "A11y:" prefix for accessibility tests with play functions 9. **Test in Storybook** - Run `npm run storybook` and verify all stories 10. **Use TodoWrite** - Track systematic coverage throughout **Key Improvements from User Feedback:** - ✅ Only use appropriate props (inspect component first) - ✅ Prefer "All States" over individual state stories - ✅ Use "Ex:" prefix for example/pattern stories - ✅ Use "A11y:" prefix for accessibility testing stories - ✅ Better sidebar organization and scanning Stories provide living documentation and enable visual regression testing. They're essential for component libraries and design systems built with Panda CSS.