Distribute UI across an organization

Learn to package and import your design system into other apps

From an architectural perspective, design systems are yet another frontend dependency. They are no different than 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 in other apps. We’ll also uncover timesaving 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. 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. 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 as a starting point for our design system, there are still vestiges of the initial app and scripts that create-react-app created for us. Let’s clean them up now.

First, we should add a basic README.md:

# The Learn Storybook design system

The Learn Storybook design system is a subset of the full [Storybook design system](https://github.com/storybookjs/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 at [Learn Storybook](https://learnstorybook.com).

Then, let’s create a src/index.js file to create a common entry point for using 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';

Let’s add a development dependency on @babel/cli to compile our JavaScript for release:

yarn add --dev @babel/cli

To build the package, we’ll add a script to package.json that builds our source directory into dist:

{
  "scripts": {
    "build": "BABEL_ENV=production babel src -d dist",
      ...
  }
  "babel": {
    "presets": [
      "react-app"
    ]
  }
}

We can now run yarn build to build our code into the dist directory -- we should add that directory to .gitignore too:

// ..
storybook-static
dist

Adding package metadata for publication

Finally, let’s make a couple of changes to package.json to ensure consumers of the package get all the information we need. The easiest way to do that is to run yarn init -- a command that initializes the package for publication:

yarn init

yarn init v1.16.0
question name (learnstorybook-design-system):
question version (0.1.0):
question description (Learn Storybook design system):
question entry point (dist/index.js):
question repository url (https://github.com/chromaui/learnstorybook-design-system.git):
question author (Tom Coleman <tom@thesnail.org>):
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-username>-learnstorybook-design-system).

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

{
  "name": "learnstorybook-design-system",
  "description": "Learn Storybook design system",
  "version": "0.1.0",
  "license": "MIT",
  "main": "dist/index.js",
  "repository": "https://github.com/chromaui/learnstorybook-design-system.git"
  // ...
}

Now 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. In order 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: https://www.npmjs.com/settings/<your-username>/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’ll be sure 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 from locally (later we’ll set things up to auto publish when PRs are merged to master) they should set up their own .env file following this process:

storybook-static
dist
.env

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

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

Although it is useful 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 Sep 03 2019)

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

#### Authors: 1
- Tom Coleman ([@tmeasday](https://github.com/tmeasday))

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 CHANGELOG.md
git commit -m "Changelog for v0.1.0 [skip ci]"

Now we can publish:

npm version 0.1.0 -m "Bump version to: %s [skip ci]"
npm publish

And use Auto to create a release on GitHub:

git push --follow-tags origin master
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"
  }
}

Now, when we run yarn release, we’ll step through all the steps we ran above (except using the auto-generated changelog) in an automated fashion. We’ll ensure that all commits to master are published by adding a command to our circle config:

# ...
- run: yarn test
- run: yarn chromatic test -a 2wix88i1ziu
- run: |
    if [ $CIRCLE_BRANCH = "master" ]
    then
      yarn release
    fi

We’ll also need to add an npm+GitHub token to your project’s Circle environment on the CircleCI website (https://circleci.com/gh/<your-username>/learnstorybook-design-system/edit#env-vars):

Setting environment variables on CircleCI

Now every time you merge a PR to master, 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, it’s trivial to install the dependency and start using the UI components.

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 of building UIs from the bottom up starting with components and ending with pages. During the demo, we’ll run two Storybook’s side-by-side: one for our example app and one for our design system.

Clone the example app repository from GitHub

git clone chromaui/learnstorybook-design-system-example-app

Install the dependencies and start the app’s Storybook

yarn install
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

Add your published design system as a dependency.

yarn add <your-username>-learnstorybook-design-system

Now, let’s update the example app’s .storybook/config.js to list the design system components, and to use the global styles defined by the design system. Make the following change to the file:

import React from 'react';
import { configure, addDecorator } from '@storybook/react';
import { global as designSystemGlobal } from '<your-username>-learnstorybook-design-system';

const { GlobalStyle } = designSystemGlobal;

addDecorator(story => (
  <>
    <GlobalStyle />
    {story()}
  </>
));

// automatically import all files ending in *.stories.js
configure(
  [
    require.context('../src', true, /\.stories\.js$/),
    require.context(
      '../node_modules/<your-username>-learnstorybook-design-system/dist',
      true,
      /\.stories\.(js|mdx)$/
    ),
  ],
  module
);

Example app storybook with design system stories

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.

Alternatively, you can browse your design system’s Storybook online if you deployed it to a web host earlier (see chapter 4).

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.

Navigate to the UserItem.js component in your editor. Also, find UserItem in the Storybook sidebar to see code changes update instantly with hot module reload.

Import the Avatar component.

import { Avatar } from '<your-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 'learnstorybook-design-system';

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

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

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

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 see the Avatar in UserList as well.

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. 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.

Next Chapter
Workflow
An overview of the design system workflow for frontend developers