March 9, 2022
  • All
  • Stencil
  • Tutorials
  • Design Systems
  • stencil
  • web components

How to use Storybook with Stencil

Anthony Giuliano

Storybook is an incredible tool that allows you to build, test, and document the components of your design system in isolation. Storybook provides a streamlined workflow and tons of addons to improve the developer experience. It is because of this that Storybook is used by indie developers and enterprise teams alike. In this tutorial, we are going to learn how to integrate Storybook into a Stencil project to make building, testing, and documenting even easier.

You can find all of the code for this tutorial on the Stencil Storybook repo here. If you prefer to watch a video tutorial, I recently created a video where I walk through this entire process. Check it out!

Storybook Stories

Stories are the foundation of any Storybook project. A story is a function that is meant to represent a particular state of a component. Multiple stories can be written for a single component to represent all of its different states. You can learn more about stories and how to write them from the Storybook docs. I just want to introduce the idea here, as we’ll be talking about them throughout the tutorial.

Add Storybook

The first thing we’ll want to do is add Storybook to our Stencil project. For this tutorial, I’ll be using the Stencil starter project that you get from running npm init stencil, but feel free to use any Stencil project. To add Storybook, open the terminal in your Stencil project and run:

npx sb init --type html

The Storybook init command will set up all the files and dependencies necessary for us to use Storybook. We’re also specifying our Storybook project type to be html via the --type flag. We do this because Stencil generates web components, and in this tutorial, we’re going to be using those web components in our stories as custom HTML elements.

Once installed, we can run Storybook with the command:

npm run storybook

You’ll notice that Storybook already has some example stories set up for us. These example stories come from the stories folder that Storybook created in our project. I’m going to delete this folder, as we won’t be using it in this tutorial. Nevertheless, these files can serve as a great reference point, especially if you’re new to Storybook.

Define Our Custom Elements

In our Stencil project, you’ll notice that Storybook created a .storybook folder for us. In this folder we have a preview.js file. This file is where we put any kind of global code that we may have. This is really important because it’s where we’re going to define our Stencil components, so we can use them in our stories.

To do this, add the following at the top of preview.js:

import {defineCustomElements} from '../loader';

defineCustomElements();

Here we are first importing our defineCustomElements method from the loader that Stencil generatedgenerates at build time. Calling defineCustomElements() essentially registers all of our components so they can be used in our stories. Because this loader is generated at build time, we have to run:

npm run build

Or

stencil build

Writing Our First Story

With all our configurations in place, we can now write our first story. For our story, we’ll be using the MyComponent component that is generated when you create a new Stencil project.

import { Component, Prop, h } from '@stencil/core';
import { format } from '../../utils/utils';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true,
})
export class MyComponent {
  /**
   * The first name
   */
  @Prop() first: string;

  /**
   * The middle name
   */
  @Prop() middle: string;

  /**
   * The last name
   */
  @Prop() last: string;

  private getText(): string {
    return format(this.first, this.middle, this.last);
  }

  render() {
    return <div>Hello, World! I'm {this.getText()}</div>;
  }
}

First, let’s create a file where we’ll write our stories. Inside our my-component folder, let’s create a file called my-component.stories.ts.

NOTE: This file does not have to be created within the my-component folder. You could instead have a stories folder that contains all the stories for every component. This is just the way that I like to organize things. All that matters is that your stories files end in .stories.ts/js.

The first thing we’ll add to our my-component.stories.ts file is the default export. This is an object that allows us to define all kinds of metadata about our stories. For our purposes, we’ll just provide a title for our stories. This title will manifest in the left navigation pane of our Storybook instance.

export default {
    // this creates a ‘Components’ folder and a ‘MyComponent’ subfolder
    title: 'Components/MyComponent',
};

Next we’re going to make a template for our stories. This is a common pattern that allows us to make multiple stories based on the same component. To do this, we’ll create a function called Template that takes in an args object and then returns our component with those args as attribute values.

const Template = (args) => `<my-component first="${args.first}" middle="${args.middle}" last="${args.last}"></my-component>`;

Once the template is created, we can create stories by using named exports. Our stories will bind to our Template and specify the args that we want to pass to our component.

export const Example = Template.bind({});
Example.args = {
  first: 'Winnie',
  middle: 'The',
  last: 'Pooh'
};

With this pattern, we can create all kinds of stories to demonstrate different use cases of our component. We can also use the “Controls” in Storybook to directly edit the values that are passed to our component.

Update on Save

Turning back to our Storybook instance, everything seems to be working great! Our component is rendering properly and we can use Storybook’s controls to instantly change the values we pass to the component. In addition, if we click on the “Docs” tab and then click “Show code”, we can see how the component is being used.

One thing you may notice, though, is that if you make a change to your component, that change will not be reflected in Storybook. This is because our component is being defined by our loader and that loader only updates when we perform a build. So to propagate the change, we need to rebuild our Stencil project by running:

npm run build

Or

stencil build

Once we do that, our changes will propagate through to Storybook. Unfortunately, running a build every time we make a change is not a great developer experience. To automatically build our project when a change is made, we can run:

stencil build --watch

Now Stencil will watch for any changes in our project and rebuild our project when they occur. This means when we save a change, that change will be visible in Storybook.

Conclusion

This tutorial provideds an introduction to setting up Storybook in your Stencil project. There is much, much more that you can do with Storybook and Stencil, so I encourage you to check out the Storybook docs and play around with it in your Stencil project. I can’t wait to see all the ways you use Storybook in your Stencil design system.


Anthony Giuliano