
Storybook Is a Contract, Not a Gallery
The value is not the pretty component browser. It is that every state a component can be in has to be written down, and the ugly ones stop being discovered in production.
Storybook gets sold as a component browser, and teams adopt it, build a nice grid of buttons, and quietly stop updating it. That version is not worth the maintenance.
The version that is worth it treats a story as a written contract: these are the states this component supports.
The forcing function
Writing the states is the work, and it is where the bugs surface:
export default { title: 'UI/ProjectCard', component: ProjectCard }
export const Default = { args: { title: 'Tulip Flowers', tags: ['React', 'Prisma'] } }
export const LongTitle = {
args: { title: 'A project name long enough to wrap onto three lines on a narrow phone', tags: ['React'] },
}
export const NoTags = { args: { title: 'Minimal', tags: [] } }
export const Arabic = {
args: { title: 'توليب للورود', tags: ['رياكت'] },
parameters: { direction: 'rtl' },
}
Nobody sets out to test the long title. They set out to write stories, and the long title is the second one, because once you are listing states you cannot avoid noticing that titles vary. That is the whole mechanism: the format asks a question the sprint never did.
On bilingual work the RTL story is the one that catches the icon that should have flipped and the number that should have used a different alignment.
Stories that are also tests
import { userEvent, within, expect } from '@storybook/test'
export const OpensOnClick = {
play: async ({ canvasElement }) => {
const c = within(canvasElement)
await userEvent.click(c.getByRole('button', { name: /details/i }))
await expect(c.getByRole('dialog')).toBeVisible()
},
}
The story you wrote for review runs in CI as an interaction test. That is the return that justifies the maintenance — one artefact serving documentation, review and regression testing instead of three.
The accessibility addon earns its slot
@storybook/addon-a11y runs axe against each story and reports contrast and ARIA problems in the same panel. Catching them per-component is far cheaper than catching them in a page audit where every instance of the same button is a separate finding.
Be honest about the cost
Stories rot. A component browser full of stale examples is worse than none because people trust it. So scope it: shared UI primitives and anything with more than three states. Not one-off page sections, not layout wrappers.
Where it pays off most
Handover and teams. If someone else will maintain this code, a set of stories is the fastest way to convey what a component is allowed to do. On solo short projects, I skip it — and I say so rather than pretending every project needs the same tooling.
Resources
- Repo: storybookjs/storybook
- Docs: storybook.js.org
- Video walkthroughs: YouTube: storybook react setup tutorial
- Related: Radix Primitives: behaviour without the look
Need this built properly?
I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.


