We are living the "component-based" era, where any user interface is split into small reusable pieces that we assemble into pages. Whenever it's in React, Angular, Vue.js, Svelte or whatever, we write components all day long and we needed a tool to isolate and build them the most atomic way possible. That's Storybook.
Storybook is an awesome workspace: it's simple to use, it has a lot of plugins and you are free to build your Storybook project as you want. But your stories can easily become messy, and less helpful than expected.
I work with Storybook for almost 3 years now, since then I loved working with this fantastic tool, but it also made sometimes my project confusing. It was not because of Storybook, but me! I figured out that without any structure and organization Storybook could be less convenient than expected.
So through the years, I set up some rules to run my Storybook project more clear, allowing me to maintain and debug my projects in a more efficient way. And so in this paper, I will share with you some of the best practices I figure out about any Storybook project.
Who gonna use your Storybook?
Depending on who gonna use your Storybook project, you won't build it the same way. Of course, your developer's team will be involved and they will need Storybook as a workspace.
But external devs could need to reach your stories if you coding some open source projects or building a design system that will be used by third-party teams. In this case, your Storybook should be a solid piece of documentation that explains your components' API.
Some designers could also be interested! For them, Storybook should be like a showcase. Stories are an inventory of their design system and what part is already available to be implemented.
Some plugins would allow you to download a story as a sketch file, which makes the stories as a common resource shared by front-end devs and UI designers.
Product owners can also be involved in Storybook. For them, it's an asset that demos the technical stack. Your boss could benefit from opening your stories, showing off your work from a communication perspective.
So depending on who gonna read the stories, they expect your Storybook project to give a specific kind of information and you have to keep it in mind while you maintain the code. It will force you to put emphasis on some stuff and avoid to waste time on useless things for your specific project.
Some of the points you going to read won't be necessary for the audience of your stories. But don't forget that the first that will need the stories, and try to be kind with the "future you" by keeping things clear.
Make your stories explicit
First thing off, make stuff the simplest way. While your project is growing with more and more components, you will often need some reminder on how to use a specific component. Storybook is perfect for that, but you need to be careful on some stuff:
Write your stories snippets the simplest way
You code the stories.js
files to display in Storybook a component, but it could also become documentation in itself about how to implement it.
The code needs to be clear and obvious. Your goal is to make understanding how to use your component without scratching your head.
To do so, try to be as static as possible: avoid abstractions, do repetitions if needed, avoid any kind of algorithm. Easier your code is, more straight to the point you are.
// This story source is written with the new "Component Story Format" from Storybook 5.2 import React from 'react' import ActorList from './index'; import ActorListItem from './item'; export default { 'title': 'ActorList' } // ❌ Bad export const badStory = () => { const actors = [{ name: 'Jonathan Groff', role: 'Holden Ford', isDetective: true, }, { name: 'Holt McCallany', role: 'Bill Tench', isDetective: true, }, { name: 'Cameron Britton', role: 'Edmund Kemper', isDetective: false, }] return ( <ActorList length={actors.length}> {actors.map(actor => ( <ActorListItem key={actor.name} {...actor}/> ))} </ActorList> ) } // ✅ Good export const goodStory = () => ( <ActorList length={3}> <ActorListItem name="Jonathan Groff" role="Holden Ford" isDetective /> <ActorListItem name="Holt McCallany" role="Bill Tench" isDetective /> <ActorListItem name="Cameron Britton" role="Edmund Kemper" /> </ActorList> )
In the example above, the badStory
has its logic that has nothing to do with what we want to showcase. Of course, it feels more natural to create a loop, which is how we gonna implement the <ActorListItem>
"in real life". But it makes an unnecessary abstraction on what we want to showcase which is "how to list actors with this component". The goodStory
is obvious, simple and quick to read which makes it perfect documentation.
Don't do one story with a lot of knobs, do a lot of stories with few knobs
Storybook-knobs is a helpful addon! It "allows you to edit props dynamically using the Storybook UI". It's awesome, but when you want to showcase a component with a lot of props, some renders requires the user to set knobs in a specific combination. Some edge cases render would be "hidden" in your story as it won't be obvious it exists.
To avoid this, make your components stories as each story is a slice of your component's API. Do as many stories as your components have features. As all stories are listed on the left, it will explicit everything you got covered.
For example, a <Button>
which has a theme
& a size
prop, you could do two different stories inside the Button
story suite. Then anyone reading the story suite would figure out quickly how theme
& size
props affect the render as anything is mixed up.
Of course, you will mostly use a component with several props values. So you will need to showcase how any combination of props works out. To do that, you can create a "playground"
story for each suite. This would allow devs and designers to try any possibilities offered by your component combining any prop value.
import { storiesOf } from '@storybook/react'; import { withKnobs, text, boolean, number } from '@storybook/addon-knobs'; import Button from './index'; const stories = storiesOf('Button', module); // ❌ Bad stories.add('default', () => { const themes = ['default', 'primary', 'success', 'danger', 'warning']; const sizes = ['sm', 'md', 'lg']; return ( <Button theme={select('theme', themes)} size={select('size', sizes)} > Button </Button> ); }); // ✅ Good const themes = ['default', 'primary', 'success', 'danger', 'warning']; const sizes = ['sm', 'md', 'lg']; stories.add('default', () => { return ( <Button>default button</Button> ); }); stories.add('theme', () => { const theme = select('theme', themes); return ( <Button theme={theme}>{theme} button</Button> ); }); stories.add('size', () => { const size = select('size', sizes); return ( <Button size={size}>{size} button</Button> ); }); stories.add('playground', () => { const theme = select('theme', themes); const size = select('size', sizes); const children = text('children', 'hello world !') return ( <Button theme={theme} size={size}>{children}</Button> ); });
This might look silly in the example above but as the component grows it makes more sense. As you can see, it doesn't mean you have to get rid of the knobs addon. Just don't only rely too much on it. With several stories, you will create an emphasis on every part of your component, and it will increase the DX by making every information reachable and your component behavior more predictable.
Architecture your stories as you architecture your codebase
One of the words a front-end developer writes the most is import
. With component-based libraries, we try to do small components we import them into bigger ones which are imported in even bigger, which are... you know the drill.
So if Storybook's left column can help you how to figure out where is located the component showcase, it could be a nice bonus.
Let just say you architecture your components this way :
/src
| /components
| <Button>
| /form
| <Input>
| <Checkbox>
| /container
| <SignUpForm>
| /view
| <SignUpPage>
Your stories should be titled:
Components|Button
Components|Form/Input
Components|Form/Checkbox
Container|SignUpForm
View|SignUpPage
This way the navigation bar on your Storybook page indicates, where any component is located which is a precious time saver.
Empower your documentation with DocsPage
DocsPage is a brand new feature from Storybook's latest update. It helps you to create beautiful documentation based on your stories and your component's definition.
You can, for example, display a table with all component's props listed with useful information like the type expected or the default value. Or you can add easily additional information, show snippets, and possibility even more in the future.
import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; import { Badge } from './Badge'; <Meta title="Demo/Badge" component={Badge} /> # Badge With `MDX` we write longform markdown documentation for our `Badge` component and embed Doc Blocks inline. <Props of={Badge} /> <Story name="positive"> <Badge status="positive">Positive</Badge> </Story>
If your component is public it's a great way to share how to use it. Or if the component is a bit odd/complex, you can put more emphasis on some specific API.
But it could also be a bit overkill for a simple component into a private project. So acknowledge DocsPage is available, and use it as you will.
Design your Storybook as your "components garage"
Now your Storybook project is better documentation, but remember Storybook also must be a working tool to create/enhance/fix your components. To do so, here some advice that will help you to work with your components system :
Make a "default"
story for each component
In every components-oriented library, there is the concept of props which are options that will affect the render and behavior of your components. Some are required to fill in, some others are optional.
So to figure out how looks the "vanilla" version of your component, it's better to have for each component a "default"
story that shows it off with only required props.
About the required props, they should be filled with the simplest value possible, to keep this vanilla smell around.
This will make your component's implementation more predictable as you know what it supposed to look in its simplest way. Also, the "default"
story could be seen as the comparison point with all the other stories, creating emphasis on what each of your props does (as we already talked about).
Finally, the "default"
story will be very useful when you debug or enhance your component as you can check if default usage of your component changes or not, controlling prospective regression.
Use action addon
Most of the props will affect the render of your components, but some are "event handlers". This kind of prop expects a function as value and the component will run it when a specific event happens in the component.
Filling out those props into your stories won't change anything visually, but they still are crucial because they are the link between the UI and the business logic.
So every event handler props, you should fill them with the action
function from the actions addons
. This way you will have a log for every event triggered by your component, with the parameters' values pass to the event.
import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import Button from './index'; const stories = storiesOf('Button', module); stories.add('onClick', () => { return ( <Button onClick={action('onClick')}>default button</Button> ); });
As you can see, it's pretty easy but important to control what information your component gives to its parents, and improve/debug this output.
Create stories for all your components whenever its kind
On the first hand, Storybook seems to be only designed for "dumb components" or components that care about the UI without any logical business.
But in fact, it fits any kind of components even the smart one like "containers" from redux. It may require some heavier setup like custom decorators but it still very helpful because every component deserves its story.
Isolate smart components can help you with your data workflow as your business logic is reduced to its lightest shape and you can focus on only what concerns your component.
Conclusion
While your project and your team grow, you will have more and more components in your Storybook. You will use some of them almost daily, and some will be more rarely used, so you will need fresh reminders about what this component does or how to implement it.
Storybook is also great to show off all the work already done to your designers or product owners. A well-maintained Storybook will allow them to acknowledge which is available quickly or which is missing, making your work more predictable for everyone, and also more easy to improve.
So your stories should be treated with great care!
This post is about what kind of rules I follow to make Storybook the most useful to me. But I know you guys have also great advice to share about handling a Storybook project, so leave a comment!