Using Storybook with Stencil
Storybook is a tool for developing, testing, and documenting UI components in isolation. @stencil/storybook-plugin is a Storybook framework for Stencil projects. It:
- compiles your components as Storybook loads them, so there's no Stencil build to run first
- updates the preview when you edit a component, without reloading the page
- fills in each component's controls and documentation from its props, events, slots, parts, and CSS custom properties
It supports Storybook 10.
Setup
Run the wizard from your Stencil project:
stencil add @stencil/storybook-plugin
It installs Storybook and the plugin, creates the .storybook/ config directory, and adds storybook and build-storybook scripts to your package.json.
Manual Setup
To do the same by hand, install the dependencies:
- npm
- Yarn
- pnpm
npm install --save-dev storybook @storybook/addon-docs @stencil/storybook-plugin@dev
yarn add --dev storybook @storybook/addon-docs @stencil/storybook-plugin@dev
pnpm add --save-dev storybook @storybook/addon-docs @stencil/storybook-plugin@dev
While Stencil v5 is in preview, the v5 version of the plugin is published under the dev tag.
Create .storybook/main.ts, naming the plugin as the framework:
import type { StorybookConfig } from '@stencil/storybook-plugin';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-docs'],
framework: {
name: '@stencil/storybook-plugin',
},
};
export default config;
Create .storybook/preview.tsx. The autodocs tag gives every component a generated documentation page:
import type { Preview } from '@stencil/storybook-plugin';
export const parameters: Preview['parameters'] = {
docs: {
source: {
excludeDecorators: true,
},
},
};
export const tags: Preview['tags'] = ['autodocs'];
And add the scripts:
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}
Writing Stories
Place a story file next to its component, such as src/components/my-button/my-button.stories.tsx. With the plugin installed, stencil generate creates one for each new component: its "Story" file option is selected by default.
A story file imports the component class and passes it as component:
import type { Meta, StoryObj } from '@stencil/storybook-plugin';
import { MyButton } from './my-button';
const meta = {
title: 'MyButton',
component: MyButton,
parameters: {
layout: 'centered',
},
} satisfies Meta<'my-button'>;
export default meta;
type Story = StoryObj<'my-button'>;
export const Primary: Story = {
args: {
variant: 'primary',
},
};
args are set as props on the component, and each one gets a control in Storybook's Controls panel.
Meta and StoryObj take the component's tag name, which types args from your project's components.d.ts. The plugin keeps that file up to date while Storybook runs.
Slots
Pass slot content with parameters.slots, keyed by slot name:
export const Primary: Story = {
args: {
variant: 'primary',
},
parameters: {
slots: {
default: 'Save',
},
},
};
Custom Rendering
For full control over a story's markup, give it a render function that returns JSX:
export const Disabled: Story = {
args: {
disabled: true,
},
render: (args) => <my-button {...args}>Can't press</my-button>,
};
Components That Use Other Components
Importing a component's file registers that component only. If it renders other components from your library, import their files in the story too:
import type { Meta, StoryObj } from '@stencil/storybook-plugin';
import { MyCard } from './my-card';
// my-card renders a my-button
import '../my-button/my-button';
Generated Documentation
The documentation page for each component is built from its source. It lists the component's props, events, slots, shadow parts, and CSS custom properties, with the descriptions from your JSDoc comments:
/**
* @slot - The button's label
* @part button - The native button
*/
@Component({
tag: 'my-button',
styleUrl: 'my-button.css',
encapsulation: { type: 'shadow' },
})
export class MyButton {
/** The visual style of the button */
@Prop() variant: 'primary' | 'secondary' = 'primary';
/** Emitted when the button is pressed */
@Event() myPress: EventEmitter<void>;
}
See Docs Generation for the comment syntax, including how to document CSS custom properties.
To change a control, or the documentation for one argument, set argTypes in the story's meta.
Adding Global Styles
If your components rely on a global stylesheet, add it with a .storybook/preview-head.html file, which Storybook injects into the <head> of the preview:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/my-design-tokens/dist/tokens.css" />
Running Storybook
- npm
- Yarn
- pnpm
npm run storybook
yarn storybook
pnpm run storybook
Then open http://localhost:6006.
To build a static copy to deploy, run npm run build-storybook. It's written to storybook-static/.
Learn More
For everything else Storybook can do, see the Storybook documentation.