Community Showcase #5Visual test with Chromatic
Back to Design Systems for Developers
  • Introduction
  • Architecture
  • Build
  • Review
  • Test
  • Document
  • Distribute
  • Workflow
  • Conclusion

Distribute UI across an organization

Learn to package and import your design system into other apps
This community translation has not been updated to the latest version of Storybook yet. Help us update it by applying the changes in the English guide to this translation. Pull requests are welcome.

From an architectural perspective, design systems are yet another frontend dependency. They are no different from popular dependencies like moment or lodash. UI components are code, so we can rely on established techniques for code reuse.

This chapter walks through design system distribution from packaging UI components to importing them into other apps. We’ll also uncover time-saving techniques to streamline versioning and release.

Propagate components to sites

Package the design system

Organizations have thousands of UI components spread across different apps. Previously, we extracted the most common components into our design system, and now we need to reintroduce those components back into the apps.

Our design system uses JavaScript package manager npm to handle distribution, versioning, and dependency management.

There are many valid methods for packaging design systems. Gander at design systems from Lonely Planet, Auth0, Salesforce, GitHub, and Microsoft to see a diversity in approaches. Some folks deliver each component as a separate package, and others ship all components in one package.

For nascent design systems, the most direct way is to publish a single versioned package that encapsulates:

  • 🏗 Common UI components
  • 🎨 Design tokens (a.k.a., style variables)
  • 📕 Documentation

Package a design system

Prepare your design system for export

As we used Create React App (CRA) as a starting point for our design system, there are still vestiges of the initial app. Let’s clean them up now.

First, update the to something more descriptive:

# Storybook design system tutorial

The Storybook design system tutorial is a subset of the full [Storybook design system](, created as a learning resource for those interested in learning how to write and publish a design system using best in practice techniques.

Learn more in [Storybook tutorials](

Then, let’s create a src/index.js file to create a common entry point for our design system. From this file, we’ll export all our design tokens and the components:

import * as styles from './shared/styles';
import * as global from './shared/global';
import * as animation from './shared/animation';
import * as icons from './shared/icons';

export { styles, global, animation, icons };

export * from './Avatar';
export * from './Badge';
export * from './Button';
export * from './Icon';
export * from './Link';

We'll need some additional development packages, we're going to use @babel/cli and cross-env to help us with the build process.

In your command line, issue the following command:

yarn add --dev @babel/cli cross-env

With the packages installed, we'll need to implement the build process.

Thankfully for us, Create React App (CRA) has already taken care of this for us. We'll use the existing build script and change it to build our design system to the dist directory:

  "scripts": {
    "build": "cross-env BABEL_ENV=production babel src -d dist"

With our build process implemented. We'll need to fine-tune it. Locate the babel key in your package.json and update it to the following:

"babel": {
  "presets": [
        "absoluteRuntime": false

Now we can run yarn build to build our code into the dist directory -- we should add that directory to .gitignore too, so we don't accidentally commit it:

// ...

Adding package metadata for publication

We'll need to make changes to our package.json to ensure our package consumers get all the necessary information. The easiest way to do it is simply running yarn init--a command that initializes the package for publication:

# Initializes a scoped package
yarn init --scope=@your-npm-username

yarn init v1.22.5
question name (learnstorybook-design-system): @your-npm-username/learnstorybook-design-system
question version (0.1.0):
question description (Learn Storybook design system):Storybook design systems tutorial
question entry point (dist/index.js):
question repository url (
question author (your-npm-username <>):
question license (MIT):
question private: no

The command will ask us a set of questions, some of which will be prefilled with answers, others that we’ll have to think about. You’ll need to pick a unique name for the package on npm (you won’t be able to use learnstorybook-design-system -- a good choice is @your-npm-username/learnstorybook-design-system).

All in all, it will update package.json with new values as a result of those questions:

  "name": "@your-npm-username/learnstorybook-design-system",
  "description": "Storybook design systems tutorial",
  "version": "0.1.0",
  "license": "MIT",
  "main": "dist/index.js",
  "repository": ""
  // ...
💡 For brevity purposes package scopes weren't mentioned. Using scopes allows you to create a package with the same name as a package created by another user or organization without conflict.

Now that we’ve prepared our package, we can publish it to npm for the first time!

Release management with Auto

To publish releases to npm, we’ll use a process that also updates a changelog describing changes, sets a sensible version number, and creates git tag linking that version number to a commit in our repository. To help with all those things, we’ll use an open-source tool called Auto, designed for this very purpose.

Let’s install Auto:

yarn add --dev auto

Auto is a command line tool we can use for various common tasks around release management. You can learn more about Auto on their documentation site.

Getting a GitHub and npm token

For the next few steps, Auto is going to talk to GitHub and npm. For that to work correctly, we’ll need a personal access token. You can get one of those on this page for GitHub. The token will need the repo scope.

For npm, you can create a token at the URL:;your-username&gt;/tokens.

You’ll need a token with “Read and Publish” permissions.

Let’s add that token to a file called .env in our project:

GH_TOKEN=<value you just got from GitHub>
NPM_TOKEN=<value you just got from npm>

By adding the file to .gitignore, we ensure that we don’t accidentally push this value to an open-source repository that all our users can see! This is crucial. If other maintainers need to publish the package locally (later we’ll set things up to auto-publish when a pull request is merged into the default branch), they should set up their own .env file following this process:


Create labels on GitHub

The first thing we need to do with Auto is to create a set of labels in GitHub. We’ll use these labels in the future when making changes to the package (see the next chapter), and that’ll allow auto to update the package version sensibly and create a changelog and release notes.

yarn auto create-labels

If you check on GitHub, you’ll now see a set of labels that auto would like us to use:

Set of labels created on GitHub by auto

We should tag all future PRs with one of the labels: major, minor, patch, skip-release, prerelease, internal, documentation before merging them.

Publish our first release with Auto manually

In the future, we’ll calculate new version numbers with auto via scripts, but for the first release, let’s run the commands manually to understand what they do. Let’s generate our first changelog entry:

yarn auto changelog

It will generate a long changelog entry with every commit we’ve created so far (and a warning we’ve been pushing to the default branch, which we should stop doing soon).

Although it is helpful to have an auto-generated changelog, so you don’t miss things, it’s also a good idea to manually edit it and craft the message in the most useful way for users. In this case, the users don’t need to know about all the commits along the way. Let’s make a nice simple message for our first v0.1.0 version. First undo the commit that Auto just created (but keep the changes:

git reset HEAD^

Then we’ll update the changelog and commit it:

# v0.1.0 (Tue Mar 09 2021)

- Created first version of the design system, with `Avatar`, `Badge`, `Button`, `Icon` and `Link` components.

#### Authors: 1

- [your-username](

Let’s add that changelog to git. Note that we use [skip ci] to tell CI platforms to ignore these commits, else we end up in their build and publish loop.

git add
git commit -m "Changelog for v0.1.0 [skip ci]"

Now we can publish:

npm --allow-same-version version 0.1.0 -m "Bump version to: %s [skip ci]"
npm publish --access=public
💡 Don't forget to adjust the commands accordingly if you're using yarn to publish your package.

And use Auto to create a release on GitHub:

git push --follow-tags origin main
yarn auto release

Yay! We’ve successfully published our package to npm and created a release on GitHub (with luck!).

Package published on npm

Release published to GitHub

(Note that although auto auto-generated the release notes for the first release, we've also modified them to make sense for a first version).

Set up scripts to use Auto

Let’s set up Auto to follow the same process when we want to publish the package in the future. We’ll add the following scripts to our package.json:

  "scripts": {
    "release": "auto shipit --base-branch=main"

Now, when we run yarn release, we'll go through all the steps we ran above (except using the auto-generated changelog) in an automated fashion. All commits to the default branch will be published.

Congratulations! You set up the infrastructure to manually publish your design system releases. Now learn how to automate releases with continuous integration.

Publish releases automatically

We use GitHub Actions for continuous integration. But before proceeding, we need to securely store the GitHub and NPM tokens from earlier so that Actions can access them.

Add your tokens to GitHub Secrets

GitHub Secrets allow us to store sensitive information in our repository. In a browser window, open your GitHub repository.

Click the ⚙️ Settings tab then the Secrets link in the sidebar. You'll see the following screen:

Empty GitHub secrets page

Click the New secret button. Use NPM_TOKEN for the name and paste the token you got from npm earlier in this chapter.

Filled GitHub secrets form

When you add the npm secret to your repository, you'll be able to access it as secrets.NPM_TOKEN. You don't need to set up another secret for your GitHub token. All GitHub users automatically get a secrets.GITHUB_TOKEN associated with their account.

Automate releases with GitHub Actions

Every time we merge a pull request, we want to publish the design system automatically. Create a new file called push.yml in the same folder we used earlier to publish Storybook and add the following:

# Name of our action
name: Release

# The event that will trigger the action
    branches: [main]

# what the action will do
    # The operating system it will run on
    runs-on: ubuntu-latest
    # This check needs to be in place to prevent a publish loop with auto and github actions
    if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
    # The list of steps that the action will go through
      - uses: actions/checkout@v2
      - name: Prepare repository
        run: git fetch --unshallow --tags
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
          node-version: 12.x
      - name: Cache node modules
        uses: actions/cache@v1
          path: node_modules
          key: yarn-deps-${{ hashFiles('yarn.lock') }}
          restore-keys: |
            yarn-deps-${{ hashFiles('yarn.lock') }}
      - name: Create Release
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          #👇 npm token, see to obtain it
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: |
          yarn install --frozen-lockfile
          yarn build
          yarn release

Save and commit your changes to the remote repository.

Success! Now every time you merge a PR to the default branch, it will automatically publish a new version, incrementing the version number as appropriate due to the labels you’ve added.

💡 We didn’t cover all of Auto’s many features and integrations that might be useful for growing design systems. Read the docs here.

Import the design system

Import the design system in an app

Now that our design system lives online installing the dependency and using the UI components is trivial.

Get the example app

Earlier in this tutorial, we standardized on a popular frontend stack that includes React and Styled Components. That means our example app must also use React and Styled Components to take full advantage of the design system.

💡 Other promising methods like Svelte or web components may allow you to ship framework-agnostic UI components. However, they are relatively new, under-documented, or lack widespread adoption, so they’re not included in this guide yet.

The example app uses Storybook to facilitate Component-Driven Development, an app development methodology for building UIs from the bottom, starting with components ending with pages. We’ll run two Storybooks side-by-side during the demo: one for our example app and one for our design system.

Run the following commands in your command line to set up the example app:

# Clones the files locally
npx degit chromaui/learnstorybook-design-system-example-app example-app

cd example-app

# Install the dependencies
yarn install

## Start Storybook
yarn storybook

You should see the Storybook running with the stories for the simple components the app uses:

Initial storybook for example app

Integrating the design system

We have our design system's Storybook published. Let's add it to our example app. We can do that by updating example app’s .storybook/main.js to the following:

// .storybook/main.js

module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
+ refs: {
+   'design-system': {
+     title: 'My design system',
+     //👇 The url provided by Chromatic when it was deployed
+     url: '',
+   },
+ },
  addons: [
  framework: '@storybook/react',
💡 Adding the refs key to .storybook/main.js, allows us to compose multiple Storybooks into one. This is helpful when working with big projects that might spread around multiple repositories or use different tech stacks.

You’ll now be able to browse the design system components and docs while developing the example app. Showcasing the design system during feature development increases the likelihood that developers will reuse existing components instead of wasting time inventing their own.

We have what we need, time to add our design system and start using it. Run the following command in your terminal:

yarn add @your-npm-username/learnstorybook-design-system

We'll need to use the same global styles defined in the design system, so we'll need to update .storybook/preview.js config file and add a global decorator.

import React from 'react';

// The styles imported from the design system.
import { global as designSystemGlobal } from '@your-npm-username/learnstorybook-design-system';

const { GlobalStyle } = designSystemGlobal;

 * Adds a global decorator to include the imported styles from the design system.
 * More on Storybook decorators at:
export const decorators = [
  Story => (
      <GlobalStyle />
      <Story />
 * More on Storybook parameters at:
export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },

Example app storybook with design system stories

We’ll use the Avatar component from our design system in the example app’s UserItem component. UserItem should render information about a user, including a name and profile photo.

In your editor, open the UserItem component located in src/components/UserItem.js. Also, select UserItem in your Storybook to see the code changes we're about to make instantly with hot module reload.

Import the Avatar component.

import { Avatar } from '@your-npm-username/learnstorybook-design-system';

We want to render the Avatar beside the username.

import React from 'react';

import styled from 'styled-components';

+ import { Avatar } from '@your-npm-username/learnstorybook-design-system';

const Container = styled.div`
  background: #eee;
  margin-bottom: 1em;
  padding: 0.5em;

- const Avatar = styled.img`
-   border: 1px solid black;
-   width: 30px;
-   height: 30px;
-   margin-right: 0.5em;
- `;

const Name = styled.span`
  color: #333;
  font-size: 16px;

export default ({ user: { name, avatarUrl } }) => (
+   <Avatar username={name} src={avatarUrl} />

Upon save, the UserItem component will update in Storybook to show the new Avatar component. Since UserItem is a part of the UserList component, you’ll also see the Avatar in UserList.

Example app using the Design System

There you have it! You just imported a design system component into the example app. Whenever you publish an update to the Avatar component in the design system, that change will also be reflected in the example app when you update the package.

Distribute design systems

Master the design system workflow

The design system workflow starts with developing UI components in Storybook and ends with distributing them to client apps. That’s not all though. Design systems must continually evolve to serve ever-changing product requirements, and our work has only just begun.

Chapter 8 illustrates the end-to-end design system workflow we created in this guide. We’ll see how UI changes ripple outward from the design system.

Keep your code in sync with this chapter. View 5911000 on GitHub.
Is this free guide helping you? Tweet to give kudos and help other devs find it.
Next Chapter
An overview of the design system workflow for frontend developers
✍️ Edit on GitHub – PRs welcome!
Join the community
6,171 developers and counting
WhyWhy StorybookComponent-driven UI
Open source software

Maintained by
Special thanks to Netlify and CircleCI