storybook-react-variants
v0.1.1
Published
Auto-generates a Storybook story rendering all combinations of component prop variants
Maintainers
Readme
storybook-react-variants
Generates a Storybook story that renders every combination of your component's prop variants in a matrix grid — columns × rows with axis labels — and adds a live "Available variants" checkbox control to toggle dimensions at runtime.
Installation
npm install storybook-react-variants
# or
yarn add storybook-react-variantsQuick start
// Button.stories.tsx
import { createVariantsStory } from 'storybook-react-variants'
import { Button } from './Button'
export default { component: Button }
export const Variants = createVariantsStory(Button)That's it. createVariantsStory reads __docgenInfo (injected by react-docgen-typescript-loader or Storybook's default Webpack config) and auto-detects all variant props:
- String literal unions —
'sm' | 'md' | 'lg'→ columns or rows - Booleans →
true/false
Props that are plain string, number, functions, etc. are ignored.
The returned object has render, argTypes, and args so Storybook picks it up as a standard CSF3 story with working controls.
Matrix layout
With a Button that has type: 'primary' | 'secondary' | 'ghost' and size: 'sm' | 'md' | 'lg':
sm md lg
primary │ <Button> <Button> <Button>
secondary│ <Button> <Button> <Button>
ghost │ <Button> <Button> <Button>- First prop → columns
- Second prop → rows
- 3+ props → stacked labeled blocks, one per combination of the remaining props
Full example
// Button.stories.tsx
import type { Meta } from '@storybook/react'
import { createVariantsStory } from 'storybook-react-variants'
import { Button } from './Button'
// Button props:
// type: 'primary' | 'secondary' | 'ghost'
// size: 'sm' | 'md' | 'lg'
// disabled: boolean
// label: string
const meta: Meta<typeof Button> = { component: Button }
export default meta
export const Variants = createVariantsStory(Button, {
baseArgs: { label: 'Click me' },
})This renders a 3 × 3 grid for type × size, stacked twice for disabled: true and disabled: false. In the Controls panel a "Available variants" checkbox group lets you toggle any dimension on or off without touching code.
Options
baseArgs
Props applied to every rendered variant. Use for required non-variant props.
createVariantsStory(Button, {
baseArgs: { label: 'Click me' },
})onlyArgs
Limit which props are used as variant dimensions. Everything else is ignored even if docgen detects it.
createVariantsStory(Button, {
onlyArgs: ['type', 'size'],
})overrideArgTypes
Replace or extend the auto-detected values for a prop.
createVariantsStory(Button, {
// only show two of the three sizes
overrideArgTypes: { size: ['sm', 'lg'] },
})defaultDisabledVariants
Props that start unchecked in the "Available variants" control. Everything else starts checked. Useful when a prop produces a large grid that you rarely need.
createVariantsStory(Button, {
defaultDisabledVariants: ['disabled'],
})gridComponent
Replace the built-in matrix renderer with your own. Receives VariantsGridProps:
import type { VariantsGridProps } from 'storybook-react-variants'
function MyGrid({ blocks }: VariantsGridProps) {
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 32 }}>
{blocks.map((block, i) => (
<div key={i}>
{block.label && <h4>{block.label}</h4>}
<div style={{ display: 'grid', gridTemplateColumns: `repeat(${block.colAxis?.values.length ?? 1}, auto)`, gap: 8 }}>
{block.cells.flat().map((cell, j) => (
<div key={j}>{cell}</div>
))}
</div>
</div>
))}
</div>
)
}
export const Variants = createVariantsStory(Button, {
gridComponent: MyGrid,
})variantWrapper
Wrap every rendered component instance — useful for providing context (theme, router, i18n) or adding a decorative border.
import type { PropsWithChildren } from 'react'
import type { ButtonProps } from './Button'
function DarkBackground({ children }: PropsWithChildren<{ variantProps: ButtonProps }>) {
return (
<div style={{ background: '#1a1a1a', padding: 16, borderRadius: 8 }}>
{children}
</div>
)
}
export const VariantsDark = createVariantsStory(Button, {
variantWrapper: DarkBackground,
})The wrapper receives the full variantProps object if you need to inspect what's being rendered:
function LabeledCell({ variantProps, children }: PropsWithChildren<{ variantProps: ButtonProps }>) {
return (
<div>
<div style={{ fontSize: 11, color: '#999', marginBottom: 4 }}>
{Object.entries(variantProps).map(([k, v]) => `${k}=${v}`).join(' · ')}
</div>
{children}
</div>
)
}Exported types
import type {
VariantsGridProps, // props for gridComponent
RenderedMatrixBlock, // one block in the matrix (colAxis, rowAxis, cells[][])
MatrixAxis, // { propName: string; values: unknown[] }
MatrixBlock, // internal data block before rendering
} from 'storybook-react-variants'VariantCell is also exported — a minimal styled wrapper you can use inside a custom gridComponent to match the built-in cell appearance:
import { VariantCell } from 'storybook-react-variants'How variant detection works
Detection reads Component.__docgenInfo.props which is injected at build time. Both react-docgen formats are supported:
| Format | Detected as variant |
|--------|-------------------|
| TypeScript: type: 'a' \| 'b' | ✅ string literal union |
| TypeScript: disabled: boolean | ✅ → [true, false] |
| TypeScript: label: string | ❌ ignored |
| PropTypes: PropTypes.oneOf(['a', 'b']) | ✅ |
| PropTypes: PropTypes.bool | ❌ not detected (no docgen type info) |
If your component doesn't have __docgenInfo, pass values explicitly via overrideArgTypes.
