React logo on a dark background

React is one of the leading frameworks for frontend development with JavaScript. It’s a naturally component-based approach where you assemble your app from reusable pieces of self-contained functionality.

A logical step is to separate your fundamental UI components from your scenario-specific application code. Creating a component library gives you ready-to-use building blocks that you can pull into your next project.

In this article we’ll put together a simple set of React components with Storybook, then package them using Babel. Storybook provides a convenient way to inspect components during and after their development. It’s an interface for browsing your library, experimenting with included components, and surfacing documentation.

We’re not going in-depth on any single technology in this article: instead, this is an overview guide of how to develop, package, and visualize components using the combination of React, Storybook, and Create-React-App.

What Is Storybook?

Storybook is simply a toolkit for developing and rendering components in isolation, outside the context in which they appear in your app. It provides a mechanism to build components, document their props, and provide interactive example renderings in a web-based UI. Storybook is framework-agnostic: you can use it with Angular, Vue, Ember, Svelte, and others besides React.

image of the Storybook component library landing page

Components are created by writing your regular React code and then adding a second companion file that describes the “stories” for that component. Your actual component is unchanged; Storybook gets all the information it needs from the accompanying .stories.jsfile. Storybook discovers these files automatically and uses their content to create entries in your library’s web UI.

We’ll see the model in action later on when we get to writing components. First you need to create a new React project and add Storybook to it.

Initializing Your React Project

We’re going to use the popular create-react-app (CRA) toolkit to initialize the project. This gives you everything you need to build React components. It’s also fully supported by Storybook.

Open your terminal and type this command to create your library:

npx create-react-app my-components

Press y to confirm the installation of create-react-app if you’ve never used the tool before. The installation process can take a couple of minutes. Once it’s done, head to your new my-components directory. CRA will have added React’s dependencies to your package.json and scaffolded a basic React application in the public and src directories.

image of initialising a React project with create-react-app

CRA assumes you’re developing a codebase that’ll be delivered straight to the browser. As we’re actually building a library that won’t be run as a standalone application, you can safely delete the default public and src directories if you like.

Adding Storybook

It’s simple to add Storybook to an existing CRA project. Running this command will get you everything you need:

npx sb init

image of initialising Storybook in a create-react-app project

Be prepared to wait for a couple of minutes again while Storybook’s packages are added to your project. The installer will create new .storybook and stories directories. The latter contains a set of example components. Delete this directory now as we won’t be using it.

The files within .storybook configure your Storybook server. main.js contains global settings such as the filename patterns to look for stories in. preview.js controls how stories are rendered within the Storybook web UI. References for both these files are available in the Storybook docs; for now, only one change is required.

Storybook defaults to looking for stories in your stories directory. This doesn’t make much sense for a project that’s exclusively a component library. We’ll place our components with their stories straight into the src directory, using the format src/ComponentName.js and src/ComponentName.stories.js. Change the stories field in your .storybook/main.js file to reference the src directory instead of stories:

module.exports = {
    "stories": [
        "../src/**/*.stories.@(js|jsx|ts|tsx)"
    ],
    // ...
}

This snippet means Storybook will discover stories in files within the src directory that have a .stories.js suffix; .jsx (React JSX), .ts, and .tsx (TypeScript) variants are also supported. If you don’t want to use this file structure, take the time now to adjust Storybook’s matching patterns to your liking.

Writing Your Components

Now you’re ready to write your first component. Author your components in the familiar way, using whichever approach you prefer. Here’s a simple button that we want to use throughout all our frontend projects:

import PropTypes from "prop-types";
 
const styles = {
    background: "#fff",
    border: "0.2rem solid #0099ff",
    color: "#0099ff",
    letterSpacing: "0.1em",
    fontWeight: "bold",
    padding: "1em",
    textTransform: "uppercase"
};
 
const Button = ({disabled, label, onClick}) => (
    <button disabled={(disabled ? "true" : "")} onClick={onClick} style={styles}>
        {label}
    </button>
);
 
Button.propTypes = {
    disabled: PropTypes.bool,
    label: PropTypes.label,
    onClick: PropTypes.func
};
 
Button.defaultProps = {
    disabled: false
};
 
export default Button;

Next create the component’s story file. This is how Storybook will find the component and understand its configuration.

import Button from "./Button.js";
 
export default {
    title: "Button",
    component: Button,
    args: {
        label: "Demo Button"
    }
};
 
const Template = args => <Button {...args} />;
 
const Standard = Template.bind({});
 
const Disabled = Template.bind({});
Disabled.args = {disabled: true, label: "Disabled Button"};
 
export {Standard, Disabled};

The module’s default export provides metadata to Storybook. This needs to be an object that includes title and component properties. The title is used to label your component in the Storybook UI; component is the component function or class you’re exposing.

Storybook’s args are equivalent to React’s props. The args property of the default export effectively sets default prop values to apply to component instances rendered by Storybook. Here buttons receive a label of Demo Button if the prop’s not changed later.

Your module’s named exports define the actual component instances which will be presented in your Storybook. At least one is required. Two are created here, the Standard button in its default state, and a Disabled button which sets the disabled prop to true.

Now start the Storybook development server:

npm run storybook

of starting the Storybook dev server

Visit localhost:6006 in your browser to view your component library. You should see your Button component in the sidebar with its two named story variations. Clicking one of the stories will show you the component’s rendered state.

image of Storybook showing a button component rendered in the Storybook UI

The “Controls” tab below the rendering canvas lets you dynamically change prop values within the Storybook UI. This makes it quick and easy to experiment with different combinations of props when you’re discovering components created by others. There are a few different ways in which Storybook can find controls; in this case, they’re coming from the propTypes assigned to the Button component.

image of Storybook showing a button component rendered in the Storybook UI

Storybook automatically handles component “actions” such as our button’s onClick prop. In a real app, you should supply a function to this prop that’ll be called when the button is clicked. Within Storybook, clicking the button logs an event to the “Actions” tab below the canvas. This includes the name of the called prop and the parameters that would have been passed to its callback.

Building With Babel

Now we’ve written a simple React component, created a story for it, and used Storybook to check the component renders in the way we expected. The next step is to build your component library and package it with npm, ready for inclusion in your next application.

Unfortunately you can’t just npm publish your raw JavaScript files. Create React App won’t transpile JSX within packages in your app’s node_modules folder, so you’d get a build error when trying to run a project with your components. You need to transpile your component library before publication by using a tool like Babel.

Begin by adding an src/index.js file that will export your module’s public API:

import Button from "./Button.js";
export {Button};

This will let your package’s users access the Button module by writing:

import {Button} from "@example/example-components";

It gives you the freedom to change file paths in the future without impacting your library’s users. Your package’s public API is now defined by the exports of index.js.

Next add Babel to your project with the following command:

npm install --save-dev 
    @babel/cli 
    @babel/plugin-transform-react-jsx 
    @babel/preset-env 
    @babel/preset-react

Create a .babelrc file at the root of your project with this content:

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ],
    "plugins": [
        [
            "@babel/plugin-transform-react-jsx",
            {
                "runtime": "automatic"
            }
        ]
    ]
}

This Babel configuration activates support for React with the new JSX transform. It means you don’t need to import React from "react"; at the top of every file that uses JSX.

Finally, add the following lines to the scripts section of your package.json file:

"scripts": {
    "prepare": "npm run dist",
    "dist": "rm -rf dist/* && babel src/ --out-dir dist --copy-files --no-copy-ignored --ignore src/**/*.stories.js"
}

The prepare script is automatically executed by npm before your package is published to a registry. It’s used here to compile your components each time you push a new version.

You can now run npm run dist to create a distribution-ready build of your library. The output files will be deposited to the dist directory. It’s a good idea to add this to your .gitignore file.

image of using Babel to transpile React components

There’s two changes left to make. First npm needs to be instructed to publish just the built files in your dist directory. This is controlled via the files field in your package.json. The second tweak is to reference the compiled version of your index.js as the package’s entrypoint using the main field:

{
    "files": [
        "dist"      
    ],
    "main": "dist/index.js"
}

You’re done! Now you can npm publish your package and npm install it in one of your applications. The downloaded package will contain just the compiled code, stripped of JSX and ready-to-use in your project. Try it out with a minimal example in a new CRA app:

import {Button} from "@example/example-components";
 
export default () => <Button />;

Your component should appear the same as its Storybook rendering. Any discrepancies will be down to the presence of global styles leaking in from your application’s CSS.

Conclusion

It takes a little upfront work to put together a React component library. You need to write the components themselves, find a way to inspect them during development, then offer a mechanism for users to discover, try, and learn about the available components. When it’s time to publish your package, you need to set up transpilation and configure npm to serve your compiled files.

Storybook solves the first of these challenges by providing a dedicated interface for rendering and experimenting with components. It’s easy to integrate with Create React App, requires no changes to your actual components, and overlaps nicely with React’s concepts.

You can solve the distribution issues by using Babel to produce transpiled builds of your components before you publish them. npm’s main and files fields are helpful to control what gets packaged and provide a convenient public API to users. Once you’re done setting it up, you can publish your package to the npm registry or your own private server, then import your pre-built components wherever you need them.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »