Files
gh-shaunrfox-okshaun-claude…/skills/panda-create-stories.md
2025-11-30 08:56:16 +08:00

1324 lines
33 KiB
Markdown

---
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<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
// 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<typeof Button>
```
### 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<typeof Button>
```
### 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<typeof Button>
```
## 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: () => (
<Box display="flex" flexDirection="column" gap="20">
{/* Primary variants */}
<Box display="flex" gap="12">
<Button variant="primary" size="small">Small</Button>
<Button variant="primary" size="medium">Medium</Button>
<Button variant="primary" size="large">Large</Button>
</Box>
{/* Secondary variants */}
<Box display="flex" gap="12">
<Button variant="secondary" size="small">Small</Button>
<Button variant="secondary" size="medium">Medium</Button>
<Button variant="secondary" size="large">Large</Button>
</Box>
{/* Outline variants */}
<Box display="flex" gap="12">
<Button variant="outline" size="small">Small</Button>
<Button variant="outline" size="medium">Medium</Button>
<Button variant="outline" size="large">Large</Button>
</Box>
{/* Ghost variants */}
<Box display="flex" gap="12">
<Button variant="ghost" size="small">Small</Button>
<Button variant="ghost" size="medium">Medium</Button>
<Button variant="ghost" size="large">Large</Button>
</Box>
</Box>
),
}
```
### 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 (
<Button
variant="primary"
onClick={() => setIsOn(!isOn)}
aria-pressed={isOn}
>
{isOn ? 'On' : 'Off'}
</Button>
)
},
}
export const ExFormIntegration: Story = {
name: 'Ex: Form Integration',
render: () => (
<form onSubmit={(e) => { e.preventDefault(); alert('Submitted!') }}>
<Button type="submit" variant="primary">Submit Form</Button>
</form>
),
}
export const ExLoadingSimulation: Story = {
name: 'Ex: Loading Simulation',
render: () => {
const [loading, setLoading] = useState(false)
const handleClick = () => {
setLoading(true)
setTimeout(() => setLoading(false), 2000)
}
return (
<Button variant="primary" loading={loading} onClick={handleClick}>
{loading ? 'Loading...' : 'Click to Load'}
</Button>
)
},
}
```
**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: () => (
<Box display="flex" gap="40">
{/* Light theme */}
<Box p="20" bg="white">
<Box mb="8" fontSize="sm" fontWeight="semibold">Light Theme</Box>
<Box display="flex" flexDirection="column" gap="12">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
</Box>
</Box>
{/* Dark theme */}
<Box className="dark" p="20" bg="slate.90">
<Box mb="8" fontSize="sm" fontWeight="semibold" color="white">Dark Theme</Box>
<Box display="flex" flexDirection="column" gap="12">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
</Box>
</Box>
</Box>
),
}
```
### Pattern 4: Responsive Behavior
Show responsive props:
```typescript
export const Responsive: Story = {
render: () => (
<Box
display={{ base: 'block', md: 'flex' }}
gap={{ md: '12' }}
>
<Button
size={{ base: 'small', md: 'medium', lg: 'large' }}
variant="primary"
>
Responsive Size
</Button>
<Button
width={{ base: 'full', md: 'auto' }}
variant="secondary"
>
Responsive Width
</Button>
</Box>
),
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: () => (
<Box display="flex" gap="12">
<Button variant="primary">First</Button>
<Button variant="primary">Second</Button>
<Button variant="primary">Third</Button>
</Box>
),
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 (
<form onSubmit={handleSubmit}>
<Box display="flex" flexDirection="column" gap="12">
<input type="text" placeholder="Name" />
<Button type="submit" variant="primary">Submit</Button>
</Box>
</form>
)
},
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<typeof CheckBox>
export default meta
type Story = StoryObj<typeof meta>
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: () => (
<Box display="flex" flexDirection="column" gap="12">
<CheckBox label="Unchecked" />
<CheckBox label="Checked" checked />
<CheckBox label="Indeterminate" indeterminate />
<CheckBox label="Disabled" disabled />
<CheckBox label="Checked Disabled" checked disabled />
<CheckBox label="Error" error />
</Box>
),
}
// Interactive checkbox group
export const CheckboxGroup: Story = {
render: () => {
const [checked, setChecked] = React.useState({
option1: false,
option2: true,
option3: false,
})
return (
<Box display="flex" flexDirection="column" gap="12">
<CheckBox
label="Option 1"
checked={checked.option1}
onChange={(e) => setChecked({ ...checked, option1: e.target.checked })}
/>
<CheckBox
label="Option 2"
checked={checked.option2}
onChange={(e) => setChecked({ ...checked, option2: e.target.checked })}
/>
<CheckBox
label="Option 3"
checked={checked.option3}
onChange={(e) => setChecked({ ...checked, option3: e.target.checked })}
/>
</Box>
)
},
}
```
## 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: () => (
<Button variant="primary">
<Icon name="check" size="16" />
<span>With Icon</span>
</Button>
),
}
```
### Custom Styling Override
Show how to override with Panda CSS props:
```typescript
export const CustomStyling: Story = {
render: () => (
<Button
variant="primary"
bg="purple.50" // Override background
px="40" // Override padding
borderRadius="full" // Override border radius
>
Custom Styled
</Button>
),
}
```
### 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 (
<Button
variant="primary"
loading={loading}
onClick={handleClick}
>
{loading ? 'Loading...' : 'Click to Load'}
</Button>
)
},
}
```
## 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 <Button>{processed}</Button>
},
}
// 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<typeof Button>
// GOOD: Document with argTypes
const meta = {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary'],
description: 'Visual style variant',
},
},
} satisfies Meta<typeof Button>
```
## 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<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
// 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: () => (
<Box display="flex" flexDirection="column" gap="20">
<Box display="flex" gap="12">
<Button variant="primary" size="small">Small</Button>
<Button variant="primary" size="medium">Medium</Button>
<Button variant="primary" size="large">Large</Button>
</Box>
<Box display="flex" gap="12">
<Button variant="secondary" size="small">Small</Button>
<Button variant="secondary" size="medium">Medium</Button>
<Button variant="secondary" size="large">Large</Button>
</Box>
<Box display="flex" gap="12">
<Button variant="outline" size="small">Small</Button>
<Button variant="outline" size="medium">Medium</Button>
<Button variant="outline" size="large">Large</Button>
</Box>
<Box display="flex" gap="12">
<Button variant="ghost" size="small">Small</Button>
<Button variant="ghost" size="medium">Medium</Button>
<Button variant="ghost" size="large">Large</Button>
</Box>
</Box>
),
}
// 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 (
<Button
variant="primary"
onClick={() => setIsOn(!isOn)}
aria-pressed={isOn}
>
{isOn ? 'On' : 'Off'}
</Button>
)
},
}
export const ExLoadingSimulation: Story = {
name: 'Ex: Loading Simulation',
render: () => {
const [loading, setLoading] = useState(false)
const handleClick = () => {
setLoading(true)
setTimeout(() => setLoading(false), 2000)
}
return (
<Button variant="primary" loading={loading} onClick={handleClick}>
{loading ? 'Loading...' : 'Click to Load'}
</Button>
)
},
}
export const ExFormIntegration: Story = {
name: 'Ex: Form Integration',
render: () => (
<form onSubmit={(e) => { e.preventDefault(); alert('Submitted!') }}>
<Box display="flex" flexDirection="column" gap="12">
<input type="text" placeholder="Name" />
<Button type="submit" variant="primary">Submit</Button>
</Box>
</form>
),
}
// 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: () => (
<Box display="flex" gap="12">
<Button variant="primary">First</Button>
<Button variant="primary">Second</Button>
<Button variant="primary">Third</Button>
</Box>
),
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.