When developing an app, you usually depend on several things, such as a running backend, a user session, a specific user role, or test data.
The problem is that the current app's state often does not reflect the state you need for your current task. Eventually, the more you add to your app, the harder it gets to reach certain states or pages. Worse, you might need to add workarounds to your code to force a specific state. Typical examples are switching between user roles or multi-step forms where every code change repeatedly forces you to complete previous steps.
Storybook simplifies working on those hard-to-reach spots in your codebase by providing an isolated workspace. It is a separate framework-agnostic app within your repository. You can do everything from developing components to documenting all different component states and showcasing them nicely using MDX with clickable demos and interactively changeable component arguments. You can also test your components within Storybook. Storybook's add-on API allows you to tailor it to your needs.
All examples use @storybook/angular v8 APIs but work similarly in other framework integrations such as React, Vue, Svelte, etc.
Tell stories with your components
The fundamental building block of Storybook is a "story", which is usually stored in a file ending with *.stories.ts
. A story represents a specific use case or state of a component.
Depending on the frontend framework, the file ending can be
*.stories.tsx
,*.stories.js
, etc.
// `meta` describes the defaults for all stories within this file
const meta: Meta<ButtonComponent> = {
title: "Example/Buttons",
component: ButtonComponent,
args: {
// Default component input arguments
label: 'Button',
primary: false,
}
}
export default meta;
type Story = StoryObj<ButtonComponent>;
export const Primary: Story = {
args: {
primary: true
}
}
export const Secondary: Story = {
args: {
primary: false
}
}
The code above creates two new menu entries, Example/Buttons/Primary
and Example/Buttons/Secondary
, using the passed in args
to render the component.
Setting up stories with clickable components can be enough, but proper documentation for others in your project is also essential. Let's explore how to create docs pages using your existing stories!
Setting up docs-pages
A "Docs" page is either auto-generated and shows all stories of a *.stories.ts
file on one page or can be created manually and filled with custom content.
To automatically generate it, set an additional property in your stories file's "meta" block.
Most "meta" level properties can also be set globally. Learn how to set up auto-generated docs globally in the official Storybook documentation.
const meta: Meta<ButtonComponent> = {
title: 'Example/Button',
component: ButtonComponent,
args: {
label: 'Button',
primary: false,
},
// automatically generate docs-pages for stories within this file
tags: ['autodocs'],
};
Out of the box, the auto-generated page contains all the stories, an additional "Show code" button to view the story code, and controls to change the component properties. Docs pages can help others in your project understand how to use a component and provide additional guidelines.
See the official documentation to learn more about creating docs pages.
Testing within Storybook
The whole testing topic deserves an entire post, but in 2024, it is also impossible to introduce Storybook without mentioning it.
There are several ways to integrate Storybook into your test suite, such as re-using story objects in your *.spec.ts
files, showing accessibility flaws directly in Storybook, or writing component and integration tests using "play" functions. I will briefly introduce the latter.
A play function is part of a story object and uses Testing Library underneath. Play functions run directly in Storybook but can also be run in CI.
import { userEvent, expect } from '@storybook/test';
// [...]
export const Primary: Story = {
args: {
primary: true,
},
play: async ({ canvas }) => {
const button = canvas.getByRole('button');
await userEvent.hover(button);
await expect(button).toHaveClass('button--hovered');
}
};
The screenshot below shows a failing test with the current and expected state.
Let me re-use the multi-step form example from the post introduction to give you a more advanced theoretical example. You could render the entire form and run through all the steps with different inputs to test your validators and other form logic, all while working on the form component in the same browser tab, which improves catching regressions early on.
Summary
Use Storybook for projects at any scale. While it is easy to integrate Storybook into an existing project, in my opinion, new projects should use it right from the beginning.
The fact that Storybook is decoupled from the backend and slices your requirements into multiple stories naturally forces you to make smarter component API decisions.
Play functions are a perfect addition to the story-authoring format. With Testing Library, you can test your components from a user's perspective rather than querying DOM nodes, for example, by ID or CSS classes.
The various options for showing stories in Storybook allow you to build your documentation for a specific audience. Depending on your requirements, you can use Storybook as a developer playground or showcase an entire design system.
Additional links
- To learn all about Storybook, a good starting point is the tutorials section on the Storybook website.
- Create a Stackblitz demo with a framework of your choice at storybook.new and play around with the examples right in the browser.
- @yannbf from the Storybook team created Mealdrop as a showcase for a larger Storybook, using many add-ons.
Top comments (0)