Design Systems for Developers
  • Introduction
  • Architecture
  • Build
  • Review
  • Test
  • Document
  • Distribute
  • Workflow
  • Conclusion
Framework:
React

Build UI components

Setup Storybook to build and catalog design system components

In chapter 3, we’ll set up the essential design system tooling starting with Storybook, the most popular component explorer. The goal of this guide is to show you how professional teams build design systems, so we’ll also focus on finer details like the code hygiene, timesaving Storybook addons, and directory structure.

Where Storybook fits in

Code formatting and linting for hygiene

Design systems are collaborative, so tools that fix syntax and standardize formatting serve to improve contribution quality. Enforcing code consistency with tooling is much less work than policing code by hand, a benefit for the resourceful design system author.

In this tutorial, we'll use VSCode as our editor, but you can apply the same principles to other modern editors like Atom, Sublime, or IntelliJ.

If we add Prettier to our project and set our editor up correctly, we should obtain consistent formatting without having to think much about it:

yarn add --dev prettier

If you are using Prettier for the first time, you may need to set it up for your editor. In VSCode, install the Prettier addon:

Prettier addon for VSCode

Enable the Format on Save editor.formatOnSave if you haven’t done so already. Once you’ve installed Prettier, you should find that it auto-formats your code whenever you save a file.

Install Storybook

Storybook is the industry-standard component explorer for developing UI components in isolation. Since design systems focus on UI components, Storybook is the ideal tool for the use case. We’ll rely on these features:

  • 📕Catalog UI components
  • 📄Save component variations as stories
  • ⚡️Developer experience tooling like Hot Module Reloading
  • 🛠Supports many view layers, including React

Install and run Storybook

# Installs Storybook
npx sb init

# Starts Storybook in development mode
yarn storybook

You should see this:

Initial Storybook UI

Nice, we’ve set up a component explorer!

Every time you install Storybook into an application, it will add some examples inside the stories folder. If you want, take some time and explore them. But we won't be needing them for our design system, so it's safe to delete the stories directory.

Now your Storybook should look like this (notice that the font styles are a little off, for instance, see the "Avatar: Initials" story):

Add global styles

Our design system requires some global styles (a CSS reset) to be applied to the document to render our components correctly. We can add them easily with the Styled Components global style tag. Update your src/shared/global.js file to the following:

src/shared/global.js
import { createGlobalStyle, css } from 'styled-components';

import { color, typography } from './styles';

+ export const fontUrl = 'https://fonts.googleapis.com/css?family=Nunito+Sans:400,700,800,900';

export const bodyStyles = css`
  /* Same as before */
`;

export const GlobalStyle = createGlobalStyle`
 body {
   ${bodyStyles}
 }`;

To use the GlobalStyle “component” in Storybook, we can make use of a decorator (a component wrapper). In an app, we’d place that component in the top-level app layout, but in Storybook, we wrap all stories using the preview config file .storybook/preview.js.

.storybook/preview.js
+ import React from 'react';

+ import { GlobalStyle } from '../src/shared/global';

/*
 * Global decorator to apply the styles to all stories
 * Read more about them at:
 * https://storybook.js.org/docs/react/writing-stories/decorators#global-decorators
 */
+ export const decorators = [
+   Story => (
+     <>
+       <GlobalStyle />
+       <Story />
+     </>
+   ),
+ ];

/*
 * Read more about global parameters at:
 * https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters
 */
export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
};

The decorator ensures the GlobalStyle renders no matter which story is selected.

💡 The <> in the decorator is not a typo--it’s a React Fragment that we use here to avoid adding an unnecessary extra HTML tag to our output.

Add font tag

Our design system also relies on the font Nunito Sans to be loaded into the app. The way to achieve that in an app depends on the app framework (read more about it here), but in Storybook, the easiest way to achieve that is to create a file, .storybook/preview-head.html, to add a <link> tag directly to the <head> of the page:

.storybook/preview-head.html
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito+Sans:400,700,800,900" />

Your Storybook should now look like this. Notice the “T” is sans-serif because we added global font styles.

Storybook with global styles loaded

Supercharge Storybook with addons

Storybook includes a powerful addon ecosystem created by a massive community. For the pragmatic developer, it’s easier to build our workflow using the ecosystem instead of creating custom tooling ourselves (which can be time-consuming).

Actions addon to verify interactivity

The actions addon gives you UI feedback in Storybook when an action is performed on an interactive element like a Button or Link. Actions comes installed in Storybook by default, and you use it simply by passing an “action” as a callback prop to a component.

Let’s see how to use it in our Button element, which optionally takes a wrapper component to respond to clicks. We have a story that passes an action to that wrapper:

src/Button.stories.js
import React from 'react';

import styled from 'styled-components';

// When the user clicks a button, it will trigger the `action()`,
// ultimately showing up in Storybook's addon panel.
function ButtonWrapper(props) {
  return <CustomButton {...props} />;
}

export const buttonWrapper = (args) => (
  return <CustomButton {...props}/>;
// … etc ..
)

Controls to stress test components

Fresh installs of Storybook include the Controls addon already configured out of the box.

It allows you to interact with component inputs (props) dynamically in the Storybook UI. You can supply multiple values to a component prop through arguments (or args for short) and adjust them through the UI. It helps design systems creators stress test component inputs (props) by adjusting the argument's values. It also gives design systems consumers the ability to try components before integrating them to understand how each input (prop) affects them.

Let's see how they work by adding a new story in the Avatar component, located in src/Avatar.stories.js:

src/Avatar.stories.js
import React from 'react';

import { Avatar } from './Avatar';

export default {
  title: 'Design System/Avatar',
  component: Avatar,
  /*
   * More on Storybook argTypes at:
   * https://storybook.js.org/docs/react/api/argtypes
   */
  argTypes: {
    size: {
      control: {
        type: 'select',
      },
      options: ['tiny', 'small', 'medium', 'large'],
    },
  },
};

// Other Avatar stories

/*
 * New story using Controls
 * Read more about Storybook templates at:
 * https://storybook.js.org/docs/react/writing-stories/introduction#using-args
 */
const Template = args => <Avatar {...args} />;

export const Controls = Template.bind({});
/*
 * More on args at:
 * https://storybook.js.org/docs/react/writing-stories/args
 */
Controls.args = {
  loading: false,
  size: 'tiny',
  username: 'Dominic Nguyen',
  src: 'https://avatars2.githubusercontent.com/u/263385',
};

Notice the Controls tab in the addon panel. Controls automatically generates graphical UI to adjust props. For instance, the “size” select element allows us to cycle through the supported Avatar sizes tiny, small, medium, and large. The same was applied to the remainder component's props (“loading”, “username”, and “src”), allowing us to create a user-friendly way to stress test components.

That said, Controls don’t replace stories. They are great for exploring the edge cases of the components and stories for showcasing the assumed states.

Interactive stories with addon-interactions

We've seen how Storybook's addons help us find edge cases with the Controls addon and how a component behaves when we interact with it using the Actions addon. Still, with each variation we add with our stories, we must check it manually and see if our design system doesn't break. Let's see how we can automate this by adding the @storybook/addon-interactions addon, and interact with our component using the play function:

Run the following command to install the addon and its dependencies:

yarn add --dev @storybook/addon-interactions @storybook/testing-library

Next, register it in Storybook's configuration file (i.e., .storybook/main.js):

./storybook/main.js
module.exports = {
  stories: [
     '../src/**/*.stories.mdx',
     '../src/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/preset-create-react-app',
+   '@storybook/addon-interactions',
  ],
  framework: "@storybook/react",
  staticDirs: ["../public"],
};

Now, let's see how it works by adding a new story for our Button component:

src/Button.stories.js
import React from 'react';
import styled from 'styled-components';
+ import { userEvent, within } from '@storybook/testing-library';
import { Button } from './Button';
import { StoryLinkWrapper } from './StoryLinkWrapper';
export default {
  title: 'Design System/Button',
  component: Button,
};

// Other Button stories

+ // New story using the play function
+ export const WithInteractions = () => (
+   <Button
+     ButtonWrapper={StoryLinkWrapper}
+     appearance="primary"
+     href="http://storybook.js.org">
+       Button
+    </Button>
+ );
+ WithInteractions.play = async ({ canvasElement }) => {
+   // Assigns canvas to the component root element
+   const canvas = within(canvasElement);
+   await userEvent.click(canvas.getByRole("link"));
+ };

+ WithInteractions.storyName = "button with interactions";
💡 Play functions are small snippets of code that once the story finishes rendering, aided by the addon-interactions, it allows you to test scenarios otherwise impossible without human intervention. Read more about them in the official documentation.

Select your new story and notice how we were able to check how the component behaves and stays consistent—taking one additional step in making our design system more robust and bug-free without relying on human interactions.

We'll visit the Accessibility and Docs addons in later chapters.

“Storybook is a powerful frontend workshop environment tool that allows teams to design, build, and organize UI components (and even full screens!) without getting tripped up over business logic and plumbing.” – Brad Frost, Author of Atomic Design

Learn how to automate maintenance

Now that our design system components are in Storybook, we've taken one more step to create an industry-standard design system. Now it's an excellent time to commit our work to our remote repository. Then we can start thinking about how we set up the automated tooling that streamlines ongoing maintenance.

A design system, like all software, should evolve. The challenge is to ensure UI components continue to look and feel as intended as the design system grows.

In chapter 4, we’ll learn how to set up continuous integration and auto-publish the design system online for collaboration.

Keep your code in sync with this chapter. View a45c546 on GitHub.
Is this free guide helping you? Tweet to give kudos and help other devs find it.
Next Chapter
Review
Collaborate with continuous integration and visual review
✍️ Edit on GitHub – PRs welcome!
Docs
Documentation
Add Storybook to your project in less than a minute to build components faster and easier.
reactvueangularweb-components
Tutorial
Tutorials
Learn Storybook with in-depth tutorials that teaches Storybook best practices. Follow along with code samples.
Learn Storybook now
Storybook
The MIT License (MIT). Website design by @domyen and the awesome Storybook community.
StorybookDocsTutorialsReleasesAddonsBlogGet involvedUse casesSupportTeam
Subscribe
Get news, free tutorials, and Storybook tips emailed to you.

Maintained by
Chromatic
Continuous integration by
CircleCI
Hosting by
Netlify