Introducing Storybook Args
Next-gen, dynamic component examples
Storybook is the world’s most popular UI component workshop. It’s used by Airbnb, Slack, Lyft, IBM, Shopify, and thousands of top teams across the industry.
At its core, Storybook is a catalog of stories: small Javascript functions that capture isolated UI component states, making them incredibly useful for interactive development, testing, and documentation.
Now in Storybook 6.0, we’re excited to introduce Storybook Args, a foundational improvement to writing stories. Args allows stories to receive dynamic data as input arguments, unlocking a new level of story ergonomics, portability, and reuse.
Args are compatible with your existing stories but unlock new capabilities:
- 🗜 Reduce size and complexity of stories
- 🚚 Reuse fixture data across stories
- ♻️ Recycle stories in a broader variety of tools
They also open the door to powerful, next generation Storybook addons which we will introduce in a series of followup posts.
💡 Why dynamic data?
Storybook’s Component Story Format (CSF) is an emerging standard for component examples, introduced in Storybook 5.2. CSF files are ES6 modules without any Storybook-specific extensions. As such, they are simple, portable, and future-proof:
export const Basic = () => (
<Button label='hello' />
);
Writing a CSF story is easy. You instantiate your component in the state you want to show, wrap it in a function, and name it.
But it can be improved. Tom Coleman and I had an insight after researching how CSF is used across 45,000+ Github repos: if stories accepted dynamic data as inputs, it would unlock countless new use cases.
Consider the following iteration of our previous example:
export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello' };
This new version receives an Args object from “the environment” and declares the initial data it wants to receive in that object.
This trick of indirection may look like a small thing, but it’s a powerful abstraction that underpins the next generation of Storybook features. In this post, we’ll focus on the different ways Args make writing stories more efficient.
🗜 Reduce story boilerplate
Args reduces the code users need to write and maintain for each story by providing a more clear separation between a story’s data and display logic.
Consider the following CSF v1 stories:
// Button.stories.js
export const Text = () => (
<Button label="hello" background="#ff0" />
);
export const Emoji = () => (
<Button label="😀 😎 👍 💯" background="#ff0" />
);
Now consider the same stories with Args:
// Button.stories.js
const Template = (args) => <Button {...args} />;
export const Text = Template.bind({});
Text.args = { label: 'hello', background: '#ff0' };
export const Emoji = Template.bind({});
Emoji.args = { ...Text.args, label: '😀 😎 👍 💯' };
The story function boilerplate is reused across stories: Template.bind({})
makes a copy of the function, reducing code duplication.
Similarly, ...Text.args
makes a copy of the data, reducing data duplication.
The benefits become even more pronounced when writing stories for other frameworks. Consider the equivalent example for Vue:
// Button.stories.js
const Template = (args) => ({
props: Object.keys(args),
components: { MyButton },
template: `
<my-button :background="background">
{{label}}
</my-button>
`
});
export const Text = Template.bind({});
Text.args = { label: 'hello', background: '#ff0' };
export const Emoji = Template.bind({});
Emoji.args = { ...Text.args, label: '😀 😎 👍 💯' };
This pattern for Args stories is a change from earlier CSF versions, but we think you’ll love it. And if you prefer writing stories the “old” way, or incrementally adopting, Storybook is fully backwards compatible.
🚚 Reuse fixture data across stories
A second major user benefit of Args is that it provides a stylized way of organizing and sharing fixture data for your components.
You’re probably familiar with the DRY principle in programming: Don’t Repeat Yourself. But in practice, we see lots of repetition in stories.
Consider a composite component for a hypothetical task list:
// TaskList.stories.js
const owner = { login: 'shilman', avatar: ... };
export const Basic = () => (
<TaskList items={[
{ title: 'checked item', checked: true, owner },
{ title: 'checked item', checked: true, owner },
{ title: 'unchecked item', checked: false, owner },
{ title: 'unassigned item', checked: false, },
]}/>
);
Now consider the Args equivalent:
// TaskList.stories.js
import { Checked, Unchecked, Unassigned } from '../Task.stories';
const Template = (args) => <TaskList {...arg} />;
export const Basic = Template.bind({});
Basic.args = {
items: [Checked.args, Checked.args, Unchecked.args, Unassigned.args]
};
In the Args example, when Task
component’s signature changes, you only need to change Tasks stories to reflect the new schema. Because your Args stories reuse fixture data, schema changes will propagate up the story hierarchy, dramatically reducing maintenance costs.
This kind of data reuse is particularly helpful when building Storybooks for large applications, where components are hierarchically organized.
Much of Args’ design inspiration comes from Chromatic’s storybook, which contains thousands of stories ranging from atomic components all the way up to the full application pages.
Args encodes these learnings into the story language itself, baking in hard-won best practices from years of developing and using Storybook as top practitioners.
♻️ Recycle stories in other tools
A final benefit is that Args stories are much more portable than before.
One design goal for Component Story Format was to create highly portable component examples. In our announcement post, we showed how a CSF story could be recycled in a Jest unit test, and painted a bigger picture of interoperability in the frontend tooling space.
That picture is now becoming a reality as CSF is adopted across the ecosystem. It’s built into hot new React framework RedwoodJS. It powers webcomponents.dev, a powerful online IDE. And it’s being incorporated into at least one popular design tool as of this writing.
Args takes interoperability one step further by removing addon dependencies from your stories. Consider the following CSF v1 story which uses actions and knobs, two of Storybook’s most popular addons:
import { action } from '@storybook/addon-actions';
import { text } from '@storybook/addon-knobs';
export default { title: 'Button' };
export const Text = () => (
<Button
onClick={action('onClick')}
label={text('label', 'Hello')}
/>
);
This ES6 module is portable, but it depends on the @storybook/addon-actions
and @storybook/addon-knobs
, both of which rely heavily on Storybook’s runtime.
Now consider the Args equivalent:
const Template = (args) => <Button {...args} />;
export const Text = Template.bind({});
Text.args = { label: 'Hello' }
Notice that this story no longer depends on @storybook/*
packages. Args are provided “by the environment”, so we can retain actions and knobs-style functionality even without the direct story dependencies! We’ll explain more about how this works as we introduce auto-generated controls and actions in follow-up posts.
Removing these Storybook dependencies increases portability, so the story becomes much easier to consume in external design, prototyping, development, and testing tools.
Consider how the Args story from above might be recycled in Jest:
import { render, fireEvent } from '@testing-library/react';
import { Text } from './Button.stories';
it('should respond to click events', () => {
const handleClick = jest.fn();
const instance = render(
<Text {...Text.args} onClick={handleClick} />
);
fireEvent.click(instance.container.firstChild);
expect(handleClick).toHaveBeenCalled();
});
Writing the same test for the original CSF story using knobs and actions would require complex mocking. No more! And we’re excited to proliferate Args-based component examples across the front-end ecosystem.
⚡ 1 minute install
After using args for the past few months, we think all users should upgrade today. Try it out today in Storybook 6.0!
Upgrade an existing Storybook project:
npx sb upgrade
Or bootstrap Storybook into an existing app:
npx sb init
To create your first Args story, create a function that takes an Args object as its first input, and then annotate the function with the data you want to receive:
import { Button } from '@storybook/react/demo';
export default { title: 'Button/Args', component: Button };
export const Basic = (args) => <Button {...args} />;
Basic.args = { children: 'hello' };
Voilà! You’ve written your first Args story. But that’s just the tip of the iceberg. Args also supercharges addons and brings lots of new features to Storybook, which we’ll expand on over the coming weeks.
Subscribe below to learn about how Args enables:
- 🎛 Auto-generated controls for component properties,
- 📢 Auto-generated actions for event logging,
- 🛠 Custom toolbars for themes, internationalization, & context.
Get involved
Args was developed by Michael Shilman (me!) and Tom Coleman with inspiration from the addon-docs
/knobs
/context
contributors including Norbert de Langen, Filipp Riabchun, Atanas Stoyanov, and Leo Y. Li.
Storybook is maintained by 1000+ open source contributors and guided by a steering committee of top maintainers. If you are interested in contributing, check out Storybook on GitHub, create an issue, or submit a pull request. Donate on Open Collective. Chat with us in Discord — a maintainer is usually online.