# Storybook > Storybook is a frontend workshop for building UI components and pages in isolation. It helps with UI development, testing, and documentation. Current version: Version 10.4 (10.4) ## Documentation - [Storybook Docs](https://storybook.js.org/docs): Main documentation - [Full Documentation (Markdown)](https://storybook.js.org/llms-full.txt): Complete documentation in plain text for LLM consumption ## Markdown Access Append `.md` to any docs URL to get clean markdown with code examples: - `https://storybook.js.org/docs/writing-stories/decorators.md` - `https://storybook.js.org/docs/9/writing-stories/decorators.md` (Version 9) ### Query Parameters All markdown endpoints (`.md` URLs and `llms-full.txt`) support these query parameters: - `renderer` - Framework filter for code snippets (default: `react`). Options: `react`, `vue`, `angular`, `svelte`, `web-components`, `solid`, `preact`, `html`, `ember`, `qwik` - `language` - Language filter for code snippets (default: `ts`). Options: `ts`, `js` - `codeOnly` - When `true`, returns only the code snippets without prose Examples: - `GET /docs/writing-stories/decorators.md?renderer=vue&language=ts` - `GET /docs/writing-stories/decorators.md?codeOnly=true` - `GET /llms-full.txt?renderer=angular&language=js` ### Versioned Access Prefix the path with a version slug for older versions: --- # Why Storybook? ## The problem The web’s universality is pushing more complexity into the frontend. It began with responsive web design, which turned every user interface from one to 10, 100, 1000 different user interfaces. Over time, additional requirements piled on like devices, browsers, accessibility, performance, and async states. Component-driven tools like React, Vue 3, and Angular help break down complex UIs into simple components but they’re not silver bullets. As frontends grow, the number of components swells. Mature projects can contain hundreds of components that yield thousands of discrete variations. To complicate matters further, those UIs are painful to debug because they’re entangled in business logic, interactive states, and app context. The breadth of modern frontends overwhelm existing workflows. Developers must consider countless UI variations, yet aren’t equipped to develop or organize them all. You end up in a situation where UIs are tougher to build, less satisfying to work on, and brittle. ![UI multiverse](../_assets/get-started/multiverse.png) ## The solution ### Build UIs in isolation Every piece of UI is now a [component](https://www.componentdriven.org/). The superpower of components is that you don't need to spin up the whole app just to see how they render. You can render a specific variation in isolation by passing in props, mocking data, or faking events. Storybook is packaged as a small, development-only, [workshop](https://bradfrost.com/blog/post/a-frontend-workshop-environment/) that lives alongside your app. It provides an isolated iframe to render components without interference from app business logic and context. That helps you focus development on each variation of a component, even the hard-to-reach edge cases. ### Capture UI variations as “stories” When developing a component variation in isolation, save it as a story. [Stories](https://storybook.js.org/docs/writing-stories.md) are a declarative syntax for supplying props and mock data to simulate component variations. Each component can have multiple stories. Each story allows you to demonstrate a specific variation of that component to verify appearance and behavior. You write stories for granular UI component variation and then use those stories in development, testing, and documentation. ```ts // Histogram.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Histogram, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { args: { dataType: 'latency', showHistogramLabels: true, histogramAccentColor: '#1EA7FD', label: 'Latency distribution', }, }; ``` ```ts // Histogram.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Histogram, }); export const Basic = meta.story({ args: { dataType: 'latency', showHistogramLabels: true, histogramAccentColor: '#1EA7FD', label: 'Latency distribution', }, }); ``` ### Storybook keeps track of every story Storybook is an interactive directory of your UI components and their stories. In the past, you'd have to spin up the app, navigate to a page, and contort the UI into the right state. This is a huge waste of time and bogs down frontend development. With Storybook, you can skip all those steps and jump straight to working on a UI component in a specific state.
Where does Storybook fit into my project? Storybook is packaged as a small, development-only, [workshop](https://bradfrost.com/blog/post/a-frontend-workshop-environment/) that lives alongside your app. Install it by [running a command](https://storybook.js.org/docs/get-started/install.md). During development, run it in a separate node process. If you’re working on UI in isolation, the only thing you’ll need to run is Storybook.
Does Storybook work with my favorite libraries? Storybook aims to integrate with industry-standard tools and platforms to simplify setup. Thanks to our ambitious developer community, we’ve made significant progress. There are hundreds of [addons](https://storybook.js.org/addons/) and tutorials that walk through how to set up Storybook in all types of projects. If you’re using a niche framework or a recently launched tool, we might not have an integration for it yet. Consider creating a [proof of concept](https://storybook.js.org/docs/addons/writing-addons.md) yourself first to lead the way for the rest of the community.
What’s the recommended Storybook workflow? Every team is different and so is their workflow. Storybook is designed to be incrementally adoptable. Teams can gradually try features to see what works best for them. Most community members choose a [Component-Driven](https://www.componentdriven.org/) workflow. UIs are developed in isolation from the “bottom up” starting with basic components then progressively combined to assemble pages. 1. Build each component in isolation and write stories for its variations. 2. Compose small components together to enable more complex functionality. 3. Assemble pages by combining composite components. 4. Integrate pages into your project by hooking up data and business logic.
## Benefits When you write stories for components, you get a bunch of additional benefits for free. **📝 Develop UIs that are more durable** Isolate components and pages and track their use cases as [stories](https://storybook.js.org/docs/writing-stories.md). Verify hard-to-reach edge cases of UI. Use addons to mock everything a component needs—context, API requests, device features, etc. **✅ Test UIs with less effort and no flakes** Stories are a pragmatic, reproducible way of tracking UI states. Use them to spot-test the UI during development. Storybook offers built-in workflows for automated [Interaction](https://storybook.js.org/docs/writing-tests/interaction-testing.md), [Accessibility](https://storybook.js.org/docs/writing-tests/accessibility-testing.md), and [Visual](https://storybook.js.org/docs/writing-tests/visual-testing.md) testing. Or use stories as test cases by [importing them into other JavaScript testing tools](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md). **📚 Document UI for your team to reuse** Storybook is the single source of truth for your UI. Stories index all your components and their various states, making it easy for your team to find and reuse existing UI patterns. Storybook also auto-generates [documentation](https://storybook.js.org/docs/writing-docs.md) from those stories. **📤 Share how the UI actually works** Stories show how UIs actually work, not just a picture of how they're supposed to work. That keeps everyone aligned on what's currently in production. [Publish Storybook](https://storybook.js.org/docs/sharing/publish-storybook.md) to get sign-off from teammates. Or [embed](https://storybook.js.org/docs/sharing/embed.md) them in wikis, Markdown, and Figma to streamline collaboration. **🚦Automate UI workflows** Storybook is compatible with your continuous integration workflow. Add it as a CI step to automate user interface testing, review implementation with teammates, and get signoff from stakeholders. ## Write stories once, reuse everywhere Storybook is powered by [Component Story Format](https://storybook.js.org/docs/api/csf.md), an open standard based on JavaScript ES6 modules. This enables stories to interoperate between development, testing, and design tools. Each story is exported as a JavaScript function enabling you to reuse it with other tools. No vendor lock-in. Reuse stories with [Jest](https://jestjs.io/) or [Vitest](https://vitest.dev/) and [Testing Library](https://testing-library.com/) to verify interactions. Put them in [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) for visual testing. Audit story accessibility with [Axe](https://github.com/dequelabs/axe-core). Or test user flows with [Playwright](https://playwright.dev/) and [Cypress](https://www.cypress.io/). Reuse unlocks more workflows at no extra cost. --- Storybook is purpose-built to help you develop complex UIs faster with greater durability and lower maintenance. It’s used by 100s of [leading companies](https://storybook.js.org/showcase) and thousands of [developers](https://github.com/storybookjs/storybook/). > Source: https://storybook.js.org/docs/get-started/why-storybook --- # Install Storybook Use the Storybook CLI to install it in a single command. Run this inside your project’s root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` Ask your AI agent to [set up Storybook for you](https://storybook.js.org/docs/ai/setup.md). Storybook will look into your project's dependencies during its install process and provide you with the best configuration available. ## Project requirements Storybook is designed to work with a variety of frameworks and environments. If your project is using one of the packages listed here, please ensure that you have the following versions installed:
- Angular 18+ - Lit 3+ - Next.js 14+ - Node.js 20+ - npm 10+ - pnpm 9+ - Preact 8+ - React Native 0.72+ - React Native Web 0.19+ - Svelte 5+ - SvelteKit 1+ - TypeScript 4.9+ - Vite 5+ - Vitest 3+ - Vue 3+ - Webpack 5+ - Yarn 4+
Additionally, the Storybook app supports the following browsers: - Chrome 131+ - Edge 134+ - Firefox 136+ - Safari 18.3+ - Opera 117+
How do I use Storybook with older browsers? You can use Storybook with older browsers in two ways: 1. Use a version of Storybook prior to `9.0.0`, which will have less strict requirements. 2. Develop or build your Storybook in ["preview-only" mode](https://storybook.js.org/docs/sharing/publish-storybook.md#build-storybook-for-older-browsers), which can be used in older, unsupported browsers.
## Installation Run this command inside your project's root directory to install the latest version of Storybook: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ```
Install a specific version To install Storybook 8.3 or newer, you can use the `create` command with a specific version: ```shell npm create storybook@8.3 ``` ```shell pnpm create storybook@8.3 ``` To install a Storybook version prior to 8.3, you must use the `init` command: ```shell npx storybook@8.2 init ``` ```shell pnpm dlx storybook@8.2 init ``` ```shell yarn dlx storybook@8.2 init ``` For either command, you can specify either an npm tag such as `latest` or `next`, or a (partial) version number. For example: - `storybook@latest init` will initialize the latest version - `storybook@7.6.10 init` will initialize `7.6.10` - `storybook@7 init` will initialize the newest `7.x.x` version
When installing, Storybook will present you with a series of interactive prompts to help customize your installation: **New to Storybook?** Storybook will ask if you're new to Storybook. If you select "Yes": - You will be welcomed with an interactive tour - Example stories will be created to help you learn If you're experienced with Storybook, you can skip the onboarding to get a minimal setup. **What configuration should we install?** Storybook will ask what type of configuration you want to install: - **Recommended**: Includes component development, [documentation](https://storybook.js.org/docs/writing-docs/autodocs.md), [testing](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md), and [accessibility](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) features - **Minimal**: Just the essentials for component development You can also manually select these features using the `--features` flag. For example: ```bash npm create storybook@latest --features docs test a11y ``` After completing the prompts, the command above will make the following changes to your local environment: - 📦 Install the required dependencies. - 🛠 Set up the necessary scripts to run and build Storybook. - 🛠 Add the default Storybook configuration. - 📝 Add some boilerplate stories to get you started. - 📡 Set up telemetry to help us improve Storybook. Read more about it [here](https://storybook.js.org/docs/configure/telemetry.md). ## Run the Setup Wizard If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](https://storybook.js.org/docs/essentials/controls.md). ![Storybook onboarding](../_assets/get-started/example-onboarding-wizard.png) If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. ## Start Storybook Storybook comes with a built-in development server featuring everything you need for project development. Depending on your system configuration, running the `storybook` command will start the local development server, output the address for you, and automatically open the address in a new browser tab where a welcome screen greets you. ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` Storybook collects completely anonymous data to help us improve user experience. Participation is optional, and you may [opt-out](https://storybook.js.org/docs/configure/telemetry.md#how-to-opt-out) if you'd not like to share any information. ![Storybook welcome screen](../_assets/get-started/example-welcome.png) There are some noteworthy items here: - A collection of useful links for more in-depth configuration and customization options you have at your disposal. - A second set of links for you to expand your Storybook knowledge and get involved with the ever-growing Storybook community. - A few example stories to get you started.

Troubleshooting

#### Run Storybook with other package managers The Storybook CLI includes support for the industry's popular package managers (e.g., [Yarn](https://yarnpkg.com/), [npm](https://www.npmjs.com/), and [pnpm](https://pnpm.io/)) automatically detecting the one you are using when you initialize Storybook. However, if you want to use a specific package manager as the default, add the `--package-manager` flag to the installation command. For example: ```shell npm create storybook@latest --package-manager=npm ``` ```shell pnpm create storybook@latest --package-manager=npm ``` ```shell yarn create storybook --package-manager=npm ``` #### The CLI doesn't detect my framework If auto‑detection fails or you’re using a custom setup, pass the project type explicitly with `--type` when running the initializer. The allowed values are: | Type | Framework | | ---------------------- | -------------------------------- | | `angular` | Angular | | `ember` | Ember | | `html` | HTML | | `nextjs` | Next.js | | `nuxt` | Nuxt | | `preact` | Preact | | `qwik` | Qwik | | `react` | React | | `react_native` | React Native | | `react_native_web` | React Native Web | | `react_native_and_rnw` | React Native (+ Web) | | `react_scripts` | Create React App (react-scripts) | | `react_project` | React Project | | `server` | Server renderer | | `solid` | Solid | | `svelte` | Svelte | | `sveltekit` | SvelteKit | | `vue3` | Vue 3 | | `web_components` | Web Components | ```shell npm create storybook@latest --type solid ``` ```shell pnpm create storybook@latest --type solid ``` ```shell yarn create storybook --type solid ``` #### Yarn Plug'n'Play (PnP) support with Storybook If you've enabled Storybook in a project running on a new version of Yarn with [Plug'n'Play](https://yarnpkg.com/features/pnp) (PnP) enabled, you may notice that it will generate `node_modules` with some additional files and folders. This is a known constraint as Storybook relies on some directories (e.g., `.cache`) to store cache files and other data to improve performance and faster builds. You can safely ignore these files and folders, adjusting your `.gitignore` file to exclude them from the version control you're using. #### Run Storybook with Webpack 4 If you previously installed Storybook in a project that uses Webpack 4, it will no longer work. This is because Storybook now uses Webpack 5 by default. To solve this issue, we recommend you upgrade your project to Webpack 5 and then run the following command to migrate your project to the latest version of Storybook: ```shell npx storybook@latest automigrate ``` ```shell pnpm dlx storybook@latest automigrate ``` ```shell yarn dlx storybook@latest automigrate ``` #### Storybook doesn't work with an empty directory By default, Storybook is configured to detect whether you're initializing it on an empty directory or an existing project. However, if you attempt to initialize Storybook, select a Vite-based framework (e.g., [React](https://storybook.js.org/docs/get-started/frameworks/react-vite.md)) in a directory that only contains a `package.json` file, you may run into issues with [Yarn Modern](https://yarnpkg.com/getting-started). This is due to how Yarn handles peer dependencies and how Storybook is set up to work with Vite-based frameworks, as it requires the [Vite](https://vitejs.dev/) package to be installed. To solve this issue, you must install Vite manually and initialize Storybook. #### The installation process seems flaky and keeps failing If you're still running into some issues during the installation process, we encourage you to check out the following resources: - Storybook's React Vite [framework documentation](https://storybook.js.org/docs/get-started/frameworks/react-vite.md) for more information on how to set up Storybook in your React project with Vite. - Storybook's React Webpack [framework documentation](https://storybook.js.org/docs/get-started/frameworks/react-webpack5.md) for more information on how to set up Storybook in your React project with Webpack 5. - [Storybook's help documentation](https://storybook.js.org/community#support) to contact the community and ask for help.
Now that you have successfully installed Storybook and understood how it works, let's continue where you left off in the [setup wizard](#run-the-setup-wizard) and delve deeper into writing stories. Now that you installed Storybook successfully, let’s take a look at a story that was written for us. > Source: https://storybook.js.org/docs/get-started/install --- # Storybook for Angular Storybook for Angular is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Angular](https://angular.io/) applications. It uses Angular builders and integrates with [Compodoc](https://compodoc.app/) to provide automatic documentation generation. ## Install To install Storybook in an existing Angular project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```sh ng run :storybook ``` To build Storybook, run: ```sh ng run :build-storybook ``` You will find the output in the configured `outputDir` (default is `dist/storybook/`). ## Configure To make the most out of Storybook in your Angular project, you can set up Compodoc integration and Storybook [decorators](https://storybook.js.org/docs/writing-stories/decorators.md) based on your project needs. ### Compodoc You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to [generate documentation](https://storybook.js.org/docs/writing-docs/autodocs.md) for your application. In Storybook, it is useful to add explanatory comments above `@Inputs` and `@Outputs`, since these are the main elements that Storybook displays in its user interface. The `@Inputs` and `@Outputs` are elements you can interact with in Storybook, such as [controls](https://storybook.js.org/docs/essentials/controls.md). #### Automatic setup When installing Storybook via `npx storybook@latest init`, you can set up Compodoc automatically. #### Manual setup If you have already installed Storybook, you can set up Compodoc manually. Install the following dependencies: ```sh npm install --save-dev @compodoc/compodoc ``` Add the following option to your Storybook Builder: ```jsonc title="angular.json" { "projects": { "your-project": { "architect": { "storybook": { "builder": "@storybook/angular:start-storybook", "options": { // 👇 Add these "compodoc": true, "compodocArgs": [ "-e", "json", "-d", // Where to store the generated documentation. It's usually the root of your Angular project. It's not necessarily the root of your Angular Workspace! ".", ], }, }, "build-storybook": { "builder": "@storybook/angular:build-storybook", "options": { // 👇 Add these "compodoc": true, "compodocArgs": ["-e", "json", "-d", "."], }, }, }, }, }, } ``` Go to your `.storybook/preview.ts` and add the following: ### Application-wide providers If your component relies on application-wide providers, like the ones defined by [`BrowserAnimationsModule`](https://angular.dev/api/platform-browser/animations/BrowserAnimationsModule) or any other modules that use the forRoot pattern to provide a [`ModuleWithProviders`](https://angular.dev/api/core/ModuleWithProviders), you can apply the `applicationConfig` [decorator](https://storybook.js.org/docs/writing-stories/decorators.md) to all stories for that component. This will provide them with the [bootstrapApplication function](https://angular.dev/api/platform-browser/bootstrapApplication), used to bootstrap the component in Storybook. ### Angular dependencies If your component has dependencies on other Angular directives and modules, these can be supplied using the `moduleMetadata` [decorator](https://storybook.js.org/docs/writing-stories/decorators.md) either for all stories of a component or for individual stories. ```ts title="YourComponent.stories.ts" const meta: Meta = { component: YourComponent, decorators: [ // Apply metadata to all stories moduleMetadata({ // import necessary ngModules or standalone components imports: [...], // declare components that are used in the template declarations: [...], // List of providers that should be available to the root component and all its children. providers: [...], }), ], }; export default meta; type Story = StoryObj; export const Base: Story = {}; export const WithCustomProvider: Story = { decorators: [ // Apply metadata to a specific story moduleMetadata({ imports: [...], declarations: [...], providers: [...], }), ], }; ``` ## FAQ ### How do I manually install the Angular framework? The easiest way to install the Angular framework is to run the upgrade command, but you can also set it up manually. First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: Finally, update your `angular.json` to include the Storybook builder: ```jsonc title="angular.json" { "projects": { "your-project": { "architect": { "storybook": { "builder": "@storybook/angular:start-storybook", "options": { // The path to the storybook config directory "configDir": ".storybook", // The build target of your project "browserTarget": "your-project:build", // The port you want to start Storybook on "port": 6006, // More options available, documented here: // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json }, }, "build-storybook": { "builder": "@storybook/angular:build-storybook", "options": { "configDir": ".storybook", "browserTarget": "your-project:build", "outputDir": "dist/storybook/your-project", // More options available, documented here: // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json }, }, }, }, }, } ``` ### How do I migrate to an Angular Storybook builder? The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. You can run `npx storybook@latest automigrate` to let Storybook detect and automatically fix your configuration. Otherwise, you can follow the next steps to adjust your configuration manually. #### With a single project in your Angular workspace Go to your `angular.json` and add `storybook` and `build-storybook` entries in your project's `architect` section, as shown [above](#manual-setup). Then, adjust your `package.json` script section, to replace the existing Storybook scripts with the Angular CLI commands: ```diff title="package.json" { "scripts": { - "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` - "build-storybook": "build-storybook" // or `storybook build` + "storybook": "ng run `"example-project:builder:config"` | | `"debugWebpack"` | Debug the Webpack configuration
`"debugWebpack": true` | | `"tsConfig"` | Location of the TypeScript configuration file relative to the current workspace.
`"tsConfig": "./tsconfig.json"`. | | `"preserveSymlinks"` | Do not use the real path when resolving modules. If true, symlinks are resolved to their real path; otherwise, they are resolved to their symlinked path.
`"preserveSymlinks": true` | | `"port"` | Port used by Storybook.
`"port": 6006` | | `"host"` | Set up a custom host for Storybook.
`"host": "http://my-custom-host"` | | `"configDir"` | Storybook configuration directory location.
`"configDir": ".storybook"` | | `"https"` | Starts Storybook with HTTPS enabled.
`"https": true`
Requires custom certificate information. | | `"sslCa"` | Provides an SSL certificate authority.
`"sslCa": "your-custom-certificate-authority"`
Optional usage with `"https"` | | `"sslCert"` | Provides an SSL certificate.
`"sslCert": "your-custom-certificate"`
Required for `https` | | `"sslKey"` | Provides an SSL key to serve Storybook.
`"sslKey": "your-ssl-key"` | | `"smokeTest"` | Exit Storybook after successful start.
`"smokeTest": true` | | `"ci"` | Starts Storybook in CI mode (skips interactive prompts and will not open browser window).
`"ci": true` | | `"open"` | Whether to open Storybook automatically in the browser.
`"open": true` | | `"quiet"` | Filters Storybook verbose build output.
`"quiet": true` | | `"enableProdMode"` | Disable Angular's development mode, which turns off assertions and other checks within the framework.
`"enableProdMode": true` | | `"docs"` | Starts Storybook in [documentation mode](https://storybook.js.org/docs/writing-docs/build-documentation.md#preview-storybooks-documentation).
`"docs": true` | | `"compodoc"` | Execute compodoc before.
`"compodoc": true` | | `"compodocArgs"` | Compodoc [options](https://compodoc.app/guides/options.html). Options `-p` with tsconfig path and `-d` with workspace root is always given.
`"compodocArgs": ["-e", "json"]` | | `"styles"` | Provide the location of the [application's styles](https://storybook.js.org/docs/configure/styling-and-css.md#global-styles) to be used with Storybook.
`"styles": ["src/styles.css", "src/styles.scss"]` | | `"stylePreprocessorOptions"` | Provides further customization for style preprocessors resolved to the workspace root.
`"stylePreprocessorOptions": { "includePaths": ["src/styles"] }` | | `"assets"` | List of static application assets.
`"assets": ["src/assets"]` | | `"initialPath"` | URL path to be appended when visiting Storybook for the first time.
`"initialPath": "docs/configure-your-project--docs"` | | `"webpackStatsJson"` | Write Webpack Stats JSON to disk.
`"webpackStatsJson": true` | | `"previewUrl"` | Disables the default storybook preview and lets you use your own.
`"previewUrl": "iframe.html"` | | `"loglevel"` | Controls level of logging during build. Can be one of: [trace, debug, info (default), warn, error, silent].
`"loglevel": "info"` | | `"sourceMap"` | Configure [sourcemaps](https://angular.dev/reference/configs/workspace-config#source-map-configuration.).
`"sourceMap": true` | | `"experimentalZoneless"` | Configure [zoneless change detection](https://angular.dev/guide/zoneless).
`"experimentalZoneless": true` | The full list of options can be found in the Angular builder schemas: - [Build Storybook](https://github.com/storybookjs/storybook/blob/next/code/frameworks/angular/build-schema.json) - [Start Storybook](https://github.com/storybookjs/storybook/blob/next/code/frameworks/angular/start-schema.json) ## API ### Options You can pass an options object for additional configuration if needed: The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Webpack builder docs](https://storybook.js.org/docs/builders/webpack.md). > Source: https://storybook.js.org/docs/get-started/frameworks/angular --- # Storybook for Next.js with Vite Storybook for Next.js (Vite) is the **recommended** [framework](https://storybook.js.org/docs/contribute/framework.md) for developing and testing UI components in isolation for [Next.js](https://nextjs.org/) applications. It uses [Vite](https://vitejs.dev/) for faster builds, better performance and [Storybook Testing](https://storybook.js.org/docs/writing-tests.md) support. ## Install To install Storybook in an existing Next.js project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Choose between Vite and Webpack This Vite-based framework offers several advantages over the Webpack-based [`@storybook/nextjs`](https://storybook.js.org/docs/get-started/frameworks/nextjs.md) framework, and is the recommended option: - ⚡ **Faster builds** - Vite's build system is significantly faster than Webpack - 🔧 **Modern tooling** - Uses the latest build tools and optimizations - 🧪 **Better test support** - Full support for the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) and other testing features - 📦 **Simpler configuration** - No need for Babel or complex Webpack configurations - 🎯 **Better development experience** - Faster Hot Module Replacement and dev server startup Storybook will automatically detect your project and select the `nextjs-vite` framework **unless** your project has custom Webpack or Babel configurations. If you have custom configurations, Storybook will ask you which framework to install. Choose `nextjs-vite` if you're willing to migrate existing Babel or Webpack configurations to Vite. Choose `nextjs` (Webpack 5) if you need to keep your existing Webpack/Babel setup. ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure Storybook for Next.js with Vite supports many Next.js features including: - 🖼 [Image optimization](#nextjss-image-component) - 🔤 [Font optimization](#nextjs-font-optimization) - 🔀 [Routing and navigation](#nextjs-routing) - 🌐 [`next/head`](#nextjs-head) - ⤵️ [Absolute imports](#imports) - 🎨 [Styling](#styling) - 🎭 [Module mocking](#mocking-modules) - ☁️ [React Server Component (experimental)](#react-server-components-rsc) ### Next.js's Image component This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/pages/api-reference/components/image) with no configuration. #### Local images [Local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) are supported. ```jsx title="index.jsx" function Home() { return ( <>

My Homepage

Welcome to my homepage!

); } ``` #### Remote images [Remote images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#remote-images) are also supported. ```jsx title="index.jsx" export default function Home() { return ( <>

My Homepage

Welcome to my homepage!

); } ``` ### Next.js font optimization [next/font](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) is partially supported in Storybook. The packages `next/font/google` and `next/font/local` are supported. #### `next/font/google` You don't have to do anything. `next/font/google` is supported out of the box. #### `next/font/local` For local fonts you have to define the [src](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) property. The path is relative to the directory where the font loader function is called. If the following component defines your localFont like this: ```js title="src/components/MyComponent.js" const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' }); ``` The Vite-based framework automatically handles font path mapping, so you don't need to configure `staticDirs` for fonts like you would with the Webpack-based framework. #### Not supported features of `next/font` The following features are not supported (yet). Support for these features might be planned for the future: - [Support font loaders configuration in next.config.js](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) - [fallback](https://nextjs.org/docs/pages/api-reference/components/font#fallback) option - [adjustFontFallback](https://nextjs.org/docs/pages/api-reference/components/font#adjustfontfallback) option - [preload](https://nextjs.org/docs/pages/api-reference/components/font#preload) option gets ignored. Storybook handles Font loading its own way. - [display](https://nextjs.org/docs/pages/api-reference/components/font#display) option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly. #### Mocking fonts during testing Occasionally fetching fonts from Google may fail as part of your Storybook build step. It is highly recommended to mock these requests, as those failures can cause your pipeline to fail as well. Next.js [supports mocking fonts](https://github.com/vercel/next.js/blob/725ddc7371f80cca273779d37f961c3e20356f95/packages/font/src/google/fetch-css-from-google-fonts.ts#L36) via a JavaScript module located where the env var `NEXT_FONT_GOOGLE_MOCKED_RESPONSES` references. For example, using [GitHub Actions](https://www.chromatic.com/docs/github-actions): ```yaml title=".github/workflows/ci.yml" - uses: chromaui/action@latest env: #👇 the location of mocked fonts to use NEXT_FONT_GOOGLE_MOCKED_RESPONSES: ${{ github.workspace }}/mocked-google-fonts.js with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }} ``` Your mocked fonts will look something like this: ```js title="mocked-google-fonts.js" //👇 Mocked responses of google fonts with the URL as the key module.exports = { 'https://fonts.googleapis.com/css?family=Inter:wght@400;500;600;800&display=block': ` /* cyrillic-ext */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: block; src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhiJ-Ek-_EeAmM.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* more font declarations go here */ /* latin */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: block; src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }`, }; ``` ### Next.js routing [Next.js's router](https://nextjs.org/docs/pages/building-your-application/routing) is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). You should only use `next/router` in the `pages` directory. In the `app` directory, it is necessary to use `next/navigation`. #### Overriding defaults Per-story overrides can be done by adding a `nextjs.router` property onto the story [parameters](https://storybook.js.org/docs/writing-stories/parameters.md). The framework will shallowly merge whatever you put here into the router. ```ts // RouterBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: RouterBasedComponent, } satisfies Meta; export default meta; type Story = StoryObj; // Interact with the links to see the route change events in the Actions panel. export const Example: Story = { parameters: { nextjs: { router: { pathname: '/profile/[id]', asPath: '/profile/1', query: { id: '1', }, }, }, }, }; ``` ```ts // RouterBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: RouterBasedComponent, }); // Interact with the links to see the route change events in the Actions panel. export const Example = meta.story({ parameters: { nextjs: { router: { pathname: '/profile/[id]', asPath: '/profile/1', query: { id: '1', }, }, }, }, }); ``` These overrides can also be applied to [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. #### Default router The default values on the stubbed router are as follows (see [globals](https://storybook.js.org/docs/essentials/toolbars-and-globals.md#globals) for more details on how globals work). ```ts // Default router const defaultRouter = { // The locale should be configured globally: https://storybook.js.org/docs/essentials/toolbars-and-globals#globals locale: globals?.locale, asPath: '/', basePath: '/', isFallback: false, isLocaleDomain: false, isReady: true, isPreview: false, route: '/', pathname: '/', query: {}, }; ``` Additionally, the [`router` object](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html). To override these defaults, you can use [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) and [`beforeEach`](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#setting-up-and-cleaning-up): ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite export default { parameters: { nextjs: { // 👇 Override the default router properties router: { basePath: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default router method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const preview = definePreview({ parameters: { nextjs: { // 👇 Override the default router properties router: { basePath: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default router method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }); export default preview; ``` ### Next.js navigation Please note that [`next/navigation`](https://nextjs.org/docs/app/building-your-application/routing) can only be used in components/pages in the `app` directory. #### Set `nextjs.appDirectory` to `true` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true` in for that component's stories: ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, // 👈 Set this }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, // 👈 Set this }, }, }); ``` If your Next.js project uses the `app` directory for every page (in other words, it does not have a `pages` directory), you can set the parameter `nextjs.appDirectory` to `true` in the [`.storybook/preview.tsx`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file to apply it to all stories. ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite const preview: Preview = { // ... parameters: { // ... nextjs: { appDirectory: true, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default definePreview({ // ... parameters: { // ... nextjs: { appDirectory: true, }, }, }); ``` #### Overriding defaults Per-story overrides can be done by adding a `nextjs.navigation` property onto the story [parameters](https://storybook.js.org/docs/writing-stories/parameters.md). The framework will shallowly merge whatever you put here into the router. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, }, }, } satisfies Meta; export default meta; type Story = StoryObj; // Interact with the links to see the route change events in the Actions panel. export const Example: Story = { parameters: { nextjs: { navigation: { pathname: '/profile', query: { user: '1', }, }, }, }, }; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, }, }, }); // Interact with the links to see the route change events in the Actions panel. export const Example = meta.story({ parameters: { nextjs: { navigation: { pathname: '/profile', query: { user: '1', }, }, }, }, }); ``` These overrides can also be applied to [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. #### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks The `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks are supported in Storybook. You have to set the `nextjs.navigation.segments` parameter to return the segments or the params you want to use. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: ['dashboard', 'analytics'], }, }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: ['dashboard', 'analytics'], }, }, }, }); ``` With the above configuration, the component rendered in the stories would receive the following values from the hooks: ```js title="NavigationBasedComponent.js" export default function NavigationBasedComponent() { const segment = useSelectedLayoutSegment(); // dashboard const segments = useSelectedLayoutSegments(); // ["dashboard", "analytics"] const params = useParams(); // {} // ... } ``` To use `useParams`, you have to use a segments array where each element is an array containing two strings. The first string is the param key and the second string is the param value. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [ ['slug', 'hello'], ['framework', 'nextjs'], ], }, }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [ ['slug', 'hello'], ['framework', 'nextjs'], ], }, }, }, }); ``` With the above configuration, the component rendered in the stories would receive the following values from the hooks: ```js title="ParamsBasedComponent.js" export default function ParamsBasedComponent() { const segment = useSelectedLayoutSegment(); // hello const segments = useSelectedLayoutSegments(); // ["hello", "nextjs"] const params = useParams(); // { slug: "hello", framework: "nextjs" } ... } ``` These overrides can also be applied to [a single story](https://storybook.js.org/docs/api/parameters.md#story-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. The default value of `nextjs.navigation.segments` is `[]` if not set. #### Default navigation context The default values on the stubbed navigation context are as follows: ```ts // Default navigation context const defaultNavigationContext = { pathname: '/', query: {}, }; ``` Additionally, the [`router` object](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html). To override these defaults, you can use [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) and [`beforeEach`](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#setting-up-and-cleaning-up): ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite export default { parameters: { nextjs: { // 👇 Override the default navigation properties navigation: { pathname: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default navigation method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const preview = definePreview({ parameters: { nextjs: { // 👇 Override the default navigation properties navigation: { pathname: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default navigation method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }); export default preview; ``` ### Next.js Head [`next/head`](https://nextjs.org/docs/pages/api-reference/components/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head `children` are placed into the head element of the iframe that Storybook uses to render your stories. ## Next.js styling ### Sass/Scss [Global Sass/SCSS stylesheets](https://nextjs.org/docs/pages/building-your-application/styling/sass) are also supported without any additional configuration. Just import them into [the preview config file](https://storybook.js.org/docs/configure/index.md#configure-story-rendering). ```ts // .storybook/preview.tsx ``` This will automatically include any of your [custom Sass configurations](https://nextjs.org/docs/pages/building-your-application/styling/sass#customizing-sass-options) in your Next.js config file. ```ts // next.config.ts const config: NextConfig = { // Any options here are included in Sass compilation for your stories sassOptions: { includePaths: [path.join(process.cwd(), 'styles')], }, }; export default config; ``` ### CSS/Sass/Scss Modules [CSS modules](https://nextjs.org/docs/pages/building-your-application/styling/css-modules) work as expected. ```ts // src/components/Button.ts // This import will work in Storybook // Sass/Scss modules are also supported // import styles from './Button.module.scss' // import styles from './Button.module.sass' export function Button() { return ( ); } ``` ### Styled JSX The built-in CSS-in-JS solution for Next.js is [styled-jsx](https://nextjs.org/docs/pages/building-your-application/styling/css-in-js), and this framework supports that out of the box, too, with zero config. ```ts // src/components/HelloWorld.ts // This will work in Storybook function HelloWorld() { return (
Hello world

scoped!

); } export default HelloWorld; ``` ### Tailwind Tailwind in Next.js [is supported via PostCSS](https://nextjs.org/docs/app/getting-started/css#tailwind-css). Storybook will automatically handle the PostCSS config for you, including any custom PostCSS configuration, so that you can import your global CSS directly into [the preview config file](https://storybook.js.org/docs/configure/index.md#configure-story-rendering): ```ts // .storybook/preview.tsx ``` ### PostCSS Next.js lets you [customize PostCSS config](https://nextjs.org/docs/pages/building-your-application/configuring/post-css). Thus this framework will automatically handle your PostCSS config for you. ### Imports #### Absolute imports [Absolute imports](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases#absolute-imports) from the root directory are supported. ```jsx title="index.tsx" // All good! // Also good! export default function HomePage() { return ( <>

Hello World

); } ``` Also OK for global styles in `.storybook/preview.tsx`! ```js title=".storybook/preview.tsx" // ... ``` Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information. #### Module aliases [Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported. ```jsx title="index.tsx" // All good! // Also good! export default function HomePage() { return ( <>

Hello World

); } ``` #### Subpath imports As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules). To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project: ```json title="package.json" { "imports": { "#*": ["./*", "./*.ts", "./*.tsx"] } } ``` Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration. Which can then be used like this: ```jsx title="index.tsx" export default function HomePage() { return ( <>

Hello World

); } ``` ### Mocking modules Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md) to control and assert their behavior. #### Built-in mocked modules This framework provides mocks for many of Next.js' internal modules: 1. [`@storybook/nextjs-vite/cache.mock`](#storybooknextjs-vitecachemock) 2. [`@storybook/nextjs-vite/headers.mock`](#storybooknextjs-viteheadersmock) 3. [`@storybook/nextjs-vite/navigation.mock`](#storybooknextjs-vitenavigationmock) 4. [`@storybook/nextjs-vite/router.mock`](#storybooknextjs-viteroutermock) #### Mocking other modules To mock other modules, use [automocking](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#automocking) or one of the [alternative methods](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#alternative-methods) documented in the mocking modules guide. ### Runtime config Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime. In the context of Storybook with this framework, you can expect Next.js's [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) feature to work just fine. Note, because Storybook doesn't server render your components, your components will only see what they normally see on the client side (i.e. they won't see `serverRuntimeConfig` but will see `publicRuntimeConfig`). For example, consider the following Next.js config: ```js title="next.config.js" module.exports = { serverRuntimeConfig: { mySecret: 'secret', secondSecret: process.env.SECOND_SECRET, // Pass through env variables }, publicRuntimeConfig: { staticFolder: '/static', }, }; ``` Calls to `getConfig` would return the following object when called within Storybook: ```jsonc // Runtime config { "serverRuntimeConfig": {}, "publicRuntimeConfig": { "staticFolder": "/static", }, } ``` ### Custom Vite configuration You can customize the [Vite configuration](https://storybook.js.org/docs/builders/vite.md#configuration) used by Storybook in your `.storybook/main.js|ts` file. By default, Storybook's configuration extends the Vite configuration used by your project, but you can configure it to not do so. Not all Vite modifications are copy/paste-able between `next.config.js` and `.storybook/main.js|ts`. It is recommended to do your research on how to properly make your modification to Storybook's Vite config and on how [Vite works](https://vitejs.dev/guide/). ### Typescript Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box. ```json title="tsconfig.json" { "compilerOptions": { "baseUrl": ".", "paths": { "@/components/*": ["components/*"] } } } ``` ### React Server Components (RSC) (⚠️ **Experimental**) If your app uses [React Server Components (RSC)](https://nextjs.org/docs/app/building-your-application/rendering/server-components), Storybook can render them in stories in the browser. To enable this set the `experimentalRSC` feature flag in your `.storybook/main.js|ts` config: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... features: { experimentalRSC: true, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... features: { experimentalRSC: true, }, }); ``` Setting this flag automatically wraps your story in a [Suspense](https://react.dev/reference/react/Suspense) wrapper, which is able to render asynchronous components in NextJS's version of React. If this wrapper causes problems in any of your existing stories, you can selectively disable it using the `react.rsc` [parameter](https://storybook.js.org/docs/writing-stories/parameters.md) at the global/component/story level: ```ts // MyServerComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: MyServerComponent, parameters: { react: { rsc: false }, }, } satisfies Meta; export default meta; ``` ```ts // MyServerComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: MyServerComponent, parameters: { react: { rsc: false }, }, }); ``` Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To work around this, you'll need to mock out your data access layer using [Vite aliases](https://vitejs.dev/config/shared-options.html#resolve-alias) or an addon like [storybook-addon-module-mock](https://storybook.js.org/addons/storybook-addon-module-mock). If your server components access data via the network, we recommend using the [MSW Storybook Addon](https://storybook.js.org/addons/msw-storybook-addon) to mock network requests. In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). ## FAQ ### How do I migrate from the `nextjs` (Webpack 5) addon? #### Automatic migration Storybook provides a migration tool for migrating to this framework from the Webpack-based Next.js framework, [`@storybook/nextjs`](https://storybook.js.org/docs/get-started/frameworks/nextjs.md). To migrate, run this command: ```bash npx storybook automigrate nextjs-to-nextjs-vite ``` This automigration tool performs the following actions: 1. Updates `package.json` files to replace `@storybook/nextjs` with `@storybook/nextjs-vite` 2. Updates `.storybook/main.js|ts` to change the framework property 3. Scans and updates import statements in your story files and configuration files If your project has custom Webpack configurations in `.storybook/main.js|ts` (via `webpackFinal`), you'll need to migrate those to Vite configuration (via `viteFinal`) after running this automigration. See the [Vite builder documentation](https://storybook.js.org/docs/builders/vite.md#migrating-from-webpack) for more information. #### Manual migration First, install the framework: ```shell npm install --save-dev @storybook/nextjs-vite ``` ```shell pnpm add --save-dev @storybook/nextjs-vite ``` ```shell yarn add --dev @storybook/nextjs-vite ``` Then, update your `.storybook/main.js|ts` to change the framework property: ```diff // .storybook/main.ts — CSF 3 - import type { StorybookConfig } from '@storybook/your-previous-framework'; + import type { StorybookConfig } from '@storybook/nextjs-vite'; const config: StorybookConfig = { // ... - framework: '@storybook/react-webpack5', + framework: '@storybook/nextjs-vite', }; export default config; ``` ```diff // .storybook/main.ts — CSF Next 🧪 - import { defineMain } from '@storybook/your-previous-framework/node'; + import { defineMain } from '@storybook/nextjs-vite/node'; export default defineMain({ // ... - framework: '@storybook/react-webpack5', + framework: '@storybook/nextjs-vite', }); ``` If your Storybook configuration contains custom Webpack operations in [`webpackFinal`](https://storybook.js.org/docs/api/main-config/main-config-webpack-final.md), you will likely need to create equivalents in [`viteFinal`](https://storybook.js.org/docs/api/main-config/main-config-vite-final.md). For more information, see the [Vite builder documentation](https://storybook.js.org/docs/builders/vite.md#migrating-from-webpack). Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... addons: [ // ... // 👇 These can both be removed // 'storybook-addon-next', // 'storybook-addon-next-router', ], }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... addons: [ // ... // 👇 These can both be removed // 'storybook-addon-next', // 'storybook-addon-next-router', ], }); ``` ### Stories for pages/components which fetch data Next.js pages can fetch data directly within server components in the `app` directory, which often include module imports that only run in a node environment. This does not (currently) work within Storybook, because if you import from a Next.js page file containing those node module imports in your stories, your Storybook's Vite build will crash because those modules will not run in a browser. To get around this, you can extract the component in your page file into a separate file and import that pure component in your stories. Or, if that's not feasible for some reason, you can [configure Vite to handle those modules](https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-exclude) in your Storybook's [`viteFinal` configuration](https://storybook.js.org/docs/builders/vite.md#configuration). **Before** ```jsx title="app/my-page/index.jsx" async function getData() { const res = await fetch(...); // ... } // Using this component in your stories will break the Storybook build export default async function Page() { const data = await getData(); return // ... } ``` **After** ```jsx title="app/my-page/index.jsx" // Use this component in your stories async function getData() { const res = await fetch(...); // ... } export default async function Page() { const data = await getData(); return ; } ``` ### Statically imported images won't load Make sure you are treating image imports the same way you treat them when using `next/image` in normal development. Before using this framework, image imports would import the raw path to the image (e.g. `'static/media/stories/assets/logo.svg'`). Now image imports work the "Next.js way", meaning that you now get an object when importing an image. For example: ```jsonc // Image import object { "src": "static/media/stories/assets/logo.svg", "height": 48, "width": 48, "blurDataURL": "static/media/stories/assets/logo.svg", } ``` Therefore, if something in Storybook isn't showing the image properly, make sure you expect the object to be returned from an import instead of only the asset path. See [local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) for more detail on how Next.js treats static image imports. ### Error: You are importing avif images, but you don't have sharp installed. You have to install sharp in order to use image optimization features in Next.js. `sharp` is a dependency of Next.js's image optimization feature. If you see this error, you need to install `sharp` in your project. ```bash npm install sharp ``` ```bash yarn add sharp ``` ```bash pnpm add sharp ``` You can refer to the [Install `sharp` to Use Built-In Image Optimization](https://nextjs.org/docs/messages/install-sharp) in the Next.js documentation for more information. ### Should I use the Vite or Webpack version? We recommend using `@storybook/nextjs-vite` (this framework) for most projects because it offers: - Faster builds and development server startup - Better support for modern testing features like the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) - Simpler configuration without Babel - Better developer experience with faster HMR However, if your project has custom Webpack configurations that are incompatible with Vite, or you need specific Webpack features, you should use [`@storybook/nextjs`](https://storybook.js.org/docs/get-started/frameworks/nextjs.md) (Webpack 5) instead. ## API ### Modules The `@storybook/nextjs-vite` package exports several modules that enable you to [mock](#mocking-modules) Next.js's internal behavior. #### `@storybook/nextjs-vite/cache.mock` Type: `typeof import('next/cache')` This module exports mocked implementations of the `next/cache` module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const Submitted: Story = { async play({ canvas, userEvent }) { const submitButton = canvas.getByRole('button', { name: /submit/i }); await userEvent.click(saveButton); // 👇 Use any mock assertions on the function await expect(revalidatePath).toHaveBeenCalledWith('/'); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const Submitted = meta.story({ async play({ canvas, userEvent }) { const submitButton = canvas.getByRole('button', { name: /submit/i }); await userEvent.click(saveButton); // 👇 Use any mock assertions on the function await expect(revalidatePath).toHaveBeenCalledWith('/'); }, }); ``` #### `@storybook/nextjs-vite/headers.mock` Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js This module exports _writable_ mocked implementations of the `next/headers` module's exports. You can use it to set up cookies or headers that are read in your story, and to later assert that they have been called. Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers: - **`headers().append(name: string, value: string)`**: Appends the value to the header if it exists already. - **`headers().delete(name: string)`**: Deletes the header - **`headers().set(name: string, value: string)`**: Sets the header to the value provided. For cookies, you can use the existing API to write them. E.g., `cookies().set('firstName', 'Jane')`. Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`. ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const LoggedInEurope: Story = { async beforeEach() { // 👇 Set mock cookies, headers and draft mode ahead of rendering cookies().set('username', 'Sol'); headers().set('timezone', 'Central European Summer Time'); draftMode.mockReturnValue({ isEnabled: true, enable: fn(), disable: fn(), }); }, async play() { // 👇 Assert that your component called the mocks await expect(cookies().get).toHaveBeenCalledOnce(); await expect(cookies().get).toHaveBeenCalledWith('username'); await expect(headers().get).toHaveBeenCalledOnce(); await expect(headers().get).toHaveBeenCalledWith('timezone'); await expect(draftMode).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const LoggedInEurope = meta.story({ async beforeEach() { // 👇 Set mock cookies, headers and draft mode ahead of rendering cookies().set('username', 'Sol'); headers().set('timezone', 'Central European Summer Time'); draftMode.mockReturnValue({ isEnabled: true, enable: fn(), disable: fn(), }); }, async play() { // 👇 Assert that your component called the mocks await expect(cookies().get).toHaveBeenCalledOnce(); await expect(cookies().get).toHaveBeenCalledWith('username'); await expect(headers().get).toHaveBeenCalledOnce(); await expect(headers().get).toHaveBeenCalledWith('timezone'); await expect(draftMode).toHaveBeenCalled(); }, }); ``` #### `@storybook/nextjs-vite/navigation.mock` Type: `typeof import('next/navigation') & getRouter: () => ReturnType` This module exports mocked implementations of the `next/navigation` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, parameters: { nextjs: { // 👇 As in the Next.js application, next/navigation only works using App Router appDirectory: true, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const Unauthenticated: Story = { async play() { // 👇 Assert that your component called redirect() await expect(redirect).toHaveBeenCalledWith('/login', 'replace'); }, }; export const GoBack: Story = { async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, parameters: { nextjs: { // 👇 As in the Next.js application, next/navigation only works using App Router appDirectory: true, }, }, }); export const Unauthenticated = meta.story({ async play() { // 👇 Assert that your component called redirect() await expect(redirect).toHaveBeenCalledWith('/login', 'replace'); }, }); export const GoBack = meta.story({ async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }); ``` #### `@storybook/nextjs-vite/router.mock` Type: `typeof import('next/router') & getRouter: () => ReturnType` This module exports mocked implementations of the `next/router` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const GoBack: Story = { async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const GoBack = meta.story({ async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }); ``` ### Options You can pass an options object for additional configuration if needed: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... framework: { name: '@storybook/your-framework', options: { nextConfigPath: path.resolve(process.cwd(), 'next.config.js'), }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... framework: { name: '@storybook/your-framework', options: { nextConfigPath: path.resolve(process.cwd(), 'next.config.js'), }, }, }); ``` The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For Next.js with Vite, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). #### `image` Type: `object` Props to pass to every instance of `next/image`. See [next/image docs](https://nextjs.org/docs/pages/api-reference/components/image) for more details. #### `nextConfigPath` Type: `string` The absolute path to the `next.config.js` file. This is necessary if you have a custom `next.config.js` file that is not in the root directory of your project. ### Parameters This framework contributes the following [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) to Storybook, under the `nextjs` namespace: #### `appDirectory` Type: `boolean` Default: `false` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](https://storybook.js.org/docs/api/parameters.md#story-parameters), [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters), or [every story in your Storybook](https://storybook.js.org/docs/api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details. #### `navigation` Type: ```ts { asPath?: string; pathname?: string; query?: Record; segments?: (string | [string, string])[]; } ``` Default value: ```js { segments: []; } ``` The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details. #### `router` Type: ```ts { asPath?: string; pathname?: string; query?: Record; } ``` The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details. > Source: https://storybook.js.org/docs/get-started/frameworks/nextjs-vite --- # Storybook for Next.js with Webpack Storybook for Next.js (Webpack) is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Next.js](https://nextjs.org/) applications using [Webpack 5](https://webpack.js.org/). **We recommend using [`@storybook/nextjs-vite`](https://storybook.js.org/docs/get-started/frameworks/nextjs-vite.md)** for most Next.js projects. The Vite-based framework is faster, more modern, and offers better support for testing features. Use this Webpack-based framework (`@storybook/nextjs`) only if: - Your project has custom Webpack configurations that are incompatible with Vite - Your project has custom Babel configurations that require Webpack - You need specific Webpack features not available in Vite ## Install To install Storybook in an existing Next.js project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` The command will prompt you to choose between this framework and [`@storybook/nextjs-vite`](https://storybook.js.org/docs/get-started/frameworks/nextjs-vite.md). We recommend the Vite-based framework ([learn why](https://storybook.js.org/docs/get-started/frameworks/nextjs-vite.md#choose-between-vite-and-webpack)). You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure Storybook for Next.js supports many Next.js features including: - 🖼 [Image optimization](#nextjss-image-component) - 🔤 [Font optimization](#nextjs-font-optimization) - 🔀 [Routing and navigation](#nextjs-routing) - 🌐 [`next/head`](#nextjs-head) - ⤵️ [Absolute imports](#imports) - 🎨 [Styling](#styling) - 🎭 [Module mocking](#mocking-modules) - ☁️ [React Server Component (experimental)](#react-server-components-rsc) ### Next.js's Image component This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/pages/api-reference/components/image) with no configuration. #### Local images [Local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) are supported. ```jsx title="index.jsx" function Home() { return ( <>

My Homepage

Welcome to my homepage!

); } ``` #### Remote images [Remote images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#remote-images) are also supported. ```jsx title="index.jsx" export default function Home() { return ( <>

My Homepage

Welcome to my homepage!

); } ``` ### Next.js font optimization [next/font](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) is partially supported in Storybook. The packages `next/font/google` and `next/font/local` are supported. #### `next/font/google` You don't have to do anything. `next/font/google` is supported out of the box. #### `next/font/local` For local fonts you have to define the [src](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) property. The path is relative to the directory where the font loader function is called. If the following component defines your localFont like this: ```js title="src/components/MyComponent.js" const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' }); ``` ##### `staticDir` mapping You have to tell Storybook where the `fonts` directory is located, via the [`staticDirs` configuration](https://storybook.js.org/docs/api/main-config/main-config-static-dirs.md#with-configuration-objects). The `from` value is relative to the `.storybook` directory. The `to` value is relative to the execution context of Storybook. Very likely it is the root of your project. ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... staticDirs: [ { from: '../src/components/fonts', to: 'src/components/fonts', }, ], }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... staticDirs: [ { from: '../src/components/fonts', to: 'src/components/fonts', }, ], }); ``` #### Not supported features of `next/font` The following features are not supported (yet). Support for these features might be planned for the future: - [Support font loaders configuration in next.config.js](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) - [fallback](https://nextjs.org/docs/pages/api-reference/components/font#fallback) option - [adjustFontFallback](https://nextjs.org/docs/pages/api-reference/components/font#adjustfontfallback) option - [preload](https://nextjs.org/docs/pages/api-reference/components/font#preload) option gets ignored. Storybook handles Font loading its own way. - [display](https://nextjs.org/docs/pages/api-reference/components/font#display) option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly. #### Mocking fonts during testing Occasionally fetching fonts from Google may fail as part of your Storybook build step. It is highly recommended to mock these requests, as those failures can cause your pipeline to fail as well. Next.js [supports mocking fonts](https://github.com/vercel/next.js/blob/725ddc7371f80cca273779d37f961c3e20356f95/packages/font/src/google/fetch-css-from-google-fonts.ts#L36) via a JavaScript module located where the env var `NEXT_FONT_GOOGLE_MOCKED_RESPONSES` references. For example, using [GitHub Actions](https://www.chromatic.com/docs/github-actions): ```yaml title=".github/workflows/ci.yml" - uses: chromaui/action@latest env: #👇 the location of mocked fonts to use NEXT_FONT_GOOGLE_MOCKED_RESPONSES: ${{ github.workspace }}/mocked-google-fonts.js with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }} ``` Your mocked fonts will look something like this: ```js title="mocked-google-fonts.js" //👇 Mocked responses of google fonts with the URL as the key module.exports = { 'https://fonts.googleapis.com/css?family=Inter:wght@400;500;600;800&display=block': ` /* cyrillic-ext */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: block; src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhiJ-Ek-_EeAmM.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* more font declarations go here */ /* latin */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: block; src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }`, }; ``` ### Next.js routing [Next.js's router](https://nextjs.org/docs/pages/building-your-application/routing) is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). You should only use `next/router` in the `pages` directory. In the `app` directory, it is necessary to use `next/navigation`. #### Overriding defaults Per-story overrides can be done by adding a `nextjs.router` property onto the story [parameters](https://storybook.js.org/docs/writing-stories/parameters.md). The framework will shallowly merge whatever you put here into the router. ```ts // RouterBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: RouterBasedComponent, } satisfies Meta; export default meta; type Story = StoryObj; // Interact with the links to see the route change events in the Actions panel. export const Example: Story = { parameters: { nextjs: { router: { pathname: '/profile/[id]', asPath: '/profile/1', query: { id: '1', }, }, }, }, }; ``` ```ts // RouterBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: RouterBasedComponent, }); // Interact with the links to see the route change events in the Actions panel. export const Example = meta.story({ parameters: { nextjs: { router: { pathname: '/profile/[id]', asPath: '/profile/1', query: { id: '1', }, }, }, }, }); ``` These overrides can also be applied to [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. #### Default router The default values on the stubbed router are as follows (see [globals](https://storybook.js.org/docs/essentials/toolbars-and-globals.md#globals) for more details on how globals work). ```ts // Default router const defaultRouter = { // The locale should be configured globally: https://storybook.js.org/docs/essentials/toolbars-and-globals#globals locale: globals?.locale, asPath: '/', basePath: '/', isFallback: false, isLocaleDomain: false, isReady: true, isPreview: false, route: '/', pathname: '/', query: {}, }; ``` Additionally, the [`router` object](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html). To override these defaults, you can use [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) and [`beforeEach`](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#setting-up-and-cleaning-up): ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite export default { parameters: { nextjs: { // 👇 Override the default router properties router: { basePath: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default router method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const preview = definePreview({ parameters: { nextjs: { // 👇 Override the default router properties router: { basePath: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default router method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }); export default preview; ``` ### Next.js navigation Please note that [`next/navigation`](https://nextjs.org/docs/app/building-your-application/routing) can only be used in components/pages in the `app` directory. #### Set `nextjs.appDirectory` to `true` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true` in for that component's stories: ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, // 👈 Set this }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, // 👈 Set this }, }, }); ``` If your Next.js project uses the `app` directory for every page (in other words, it does not have a `pages` directory), you can set the parameter `nextjs.appDirectory` to `true` in the [`.storybook/preview.tsx`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file to apply it to all stories. ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite const preview: Preview = { // ... parameters: { // ... nextjs: { appDirectory: true, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default definePreview({ // ... parameters: { // ... nextjs: { appDirectory: true, }, }, }); ``` #### Overriding defaults Per-story overrides can be done by adding a `nextjs.navigation` property onto the story [parameters](https://storybook.js.org/docs/writing-stories/parameters.md). The framework will shallowly merge whatever you put here into the router. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, }, }, } satisfies Meta; export default meta; type Story = StoryObj; // Interact with the links to see the route change events in the Actions panel. export const Example: Story = { parameters: { nextjs: { navigation: { pathname: '/profile', query: { user: '1', }, }, }, }, }; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, }, }, }); // Interact with the links to see the route change events in the Actions panel. export const Example = meta.story({ parameters: { nextjs: { navigation: { pathname: '/profile', query: { user: '1', }, }, }, }, }); ``` These overrides can also be applied to [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. #### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks The `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks are supported in Storybook. You have to set the `nextjs.navigation.segments` parameter to return the segments or the params you want to use. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: ['dashboard', 'analytics'], }, }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: ['dashboard', 'analytics'], }, }, }, }); ``` With the above configuration, the component rendered in the stories would receive the following values from the hooks: ```js title="NavigationBasedComponent.js" export default function NavigationBasedComponent() { const segment = useSelectedLayoutSegment(); // dashboard const segments = useSelectedLayoutSegments(); // ["dashboard", "analytics"] const params = useParams(); // {} // ... } ``` To use `useParams`, you have to use a segments array where each element is an array containing two strings. The first string is the param key and the second string is the param value. ```ts // NavigationBasedComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [ ['slug', 'hello'], ['framework', 'nextjs'], ], }, }, }, } satisfies Meta; export default meta; ``` ```ts // NavigationBasedComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [ ['slug', 'hello'], ['framework', 'nextjs'], ], }, }, }, }); ``` With the above configuration, the component rendered in the stories would receive the following values from the hooks: ```js title="ParamsBasedComponent.js" export default function ParamsBasedComponent() { const segment = useSelectedLayoutSegment(); // hello const segments = useSelectedLayoutSegments(); // ["hello", "nextjs"] const params = useParams(); // { slug: "hello", framework: "nextjs" } ... } ``` These overrides can also be applied to [a single story](https://storybook.js.org/docs/api/parameters.md#story-parameters) or [all stories in your project](https://storybook.js.org/docs/api/parameters.md#project-parameters). Standard [parameter inheritance](https://storybook.js.org/docs/api/parameters.md#parameter-inheritance) rules apply. The default value of `nextjs.navigation.segments` is `[]` if not set. #### Default navigation context The default values on the stubbed navigation context are as follows: ```ts // Default navigation context const defaultNavigationContext = { pathname: '/', query: {}, }; ``` Additionally, the [`router` object](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) contains all of the original methods (such as `push()`, `replace()`, etc.) as mock functions that can be manipulated and asserted on using [regular mock APIs](https://vitest.dev/api/mock.html). To override these defaults, you can use [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) and [`beforeEach`](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#setting-up-and-cleaning-up): ```ts // .storybook/preview.tsx — CSF 3 // Replace your-framework with nextjs or nextjs-vite export default { parameters: { nextjs: { // 👇 Override the default navigation properties navigation: { pathname: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default navigation method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const preview = definePreview({ parameters: { nextjs: { // 👇 Override the default navigation properties navigation: { pathname: '/app/', }, }, }, async beforeEach() { // 👇 Manipulate the default navigation method mocks getRouter().push.mockImplementation(() => { /* ... */ }); }, }); export default preview; ``` ### Next.js Head [`next/head`](https://nextjs.org/docs/pages/api-reference/components/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head `children` are placed into the head element of the iframe that Storybook uses to render your stories. ## Next.js styling ### Sass/Scss [Global Sass/SCSS stylesheets](https://nextjs.org/docs/pages/building-your-application/styling/sass) are also supported without any additional configuration. Just import them into [the preview config file](https://storybook.js.org/docs/configure/index.md#configure-story-rendering). ```ts // .storybook/preview.tsx ``` This will automatically include any of your [custom Sass configurations](https://nextjs.org/docs/pages/building-your-application/styling/sass#customizing-sass-options) in your Next.js config file. ```ts // next.config.ts const config: NextConfig = { // Any options here are included in Sass compilation for your stories sassOptions: { includePaths: [path.join(process.cwd(), 'styles')], }, }; export default config; ``` ### CSS/Sass/Scss Modules [CSS modules](https://nextjs.org/docs/pages/building-your-application/styling/css-modules) work as expected. ```ts // src/components/Button.ts // This import will work in Storybook // Sass/Scss modules are also supported // import styles from './Button.module.scss' // import styles from './Button.module.sass' export function Button() { return ( ); } ``` ### Styled JSX The built-in CSS-in-JS solution for Next.js is [styled-jsx](https://nextjs.org/docs/pages/building-your-application/styling/css-in-js), and this framework supports that out of the box, too, with zero config. ```ts // src/components/HelloWorld.ts // This will work in Storybook function HelloWorld() { return (
Hello world

scoped!

); } export default HelloWorld; ``` You can use your own Babel config, too. This is an example of how you can customize styled-jsx. ```jsonc // .babelrc (or whatever config file you use) { "presets": [ [ "next/babel", { "styled-jsx": { "plugins": ["@styled-jsx/plugin-sass"], }, }, ], ], } ``` ### Tailwind Tailwind in Next.js [is supported via PostCSS](https://nextjs.org/docs/app/getting-started/css#tailwind-css). Storybook will automatically handle the PostCSS config for you, including any custom PostCSS configuration, so that you can import your global CSS directly into [the preview config file](https://storybook.js.org/docs/configure/index.md#configure-story-rendering): ```ts // .storybook/preview.tsx ``` ### PostCSS Next.js lets you [customize PostCSS config](https://nextjs.org/docs/pages/building-your-application/configuring/post-css). Thus this framework will automatically handle your PostCSS config for you. ### Imports #### Absolute imports [Absolute imports](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases#absolute-imports) from the root directory are supported. ```jsx title="index.tsx" // All good! // Also good! export default function HomePage() { return ( <>

Hello World

); } ``` Also OK for global styles in `.storybook/preview.tsx`! ```js title=".storybook/preview.tsx" // ... ``` Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information. #### Module aliases [Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported. ```jsx title="index.tsx" // All good! // Also good! export default function HomePage() { return ( <>

Hello World

); } ``` #### Subpath imports As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules). To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for all modules in the project: ```json title="package.json" { "imports": { "#*": ["./*", "./*.ts", "./*.tsx"] } } ``` Because subpath imports replace module aliases, you can remove the path aliases from your TypeScript configuration. Which can then be used like this: ```jsx title="index.tsx" export default function HomePage() { return ( <>

Hello World

); } ``` ### Mocking modules Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md) to control and assert their behavior. #### Built-in mocked modules This framework provides mocks for many of Next.js' internal modules: 1. [`@storybook/nextjs/cache.mock`](#storybooknextjscachemock) 2. [`@storybook/nextjs/headers.mock`](#storybooknextjsheadersmock) 3. [`@storybook/nextjs/navigation.mock`](#storybooknextjsnavigationmock) 4. [`@storybook/nextjs/router.mock`](#storybooknextjsroutermock) #### Mocking other modules To mock other modules, use [automocking](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#automocking) or one of the [alternative methods](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#alternative-methods) documented in the mocking modules guide. ### Runtime config Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime. In the context of Storybook with this framework, you can expect Next.js's [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) feature to work just fine. Note, because Storybook doesn't server render your components, your components will only see what they normally see on the client side (i.e. they won't see `serverRuntimeConfig` but will see `publicRuntimeConfig`). For example, consider the following Next.js config: ```js title="next.config.js" module.exports = { serverRuntimeConfig: { mySecret: 'secret', secondSecret: process.env.SECOND_SECRET, // Pass through env variables }, publicRuntimeConfig: { staticFolder: '/static', }, }; ``` Calls to `getConfig` would return the following object when called within Storybook: ```jsonc // Runtime config { "serverRuntimeConfig": {}, "publicRuntimeConfig": { "staticFolder": "/static", }, } ``` ### Custom Webpack config Next.js comes with a lot of things for free out of the box like Sass support, but sometimes you add [custom Webpack config modifications to Next.js](https://nextjs.org/docs/pages/api-reference/next-config-js/webpack). This framework takes care of most of the Webpack modifications you would want to add. If Next.js supports a feature out of the box, then that feature will work out of the box in Storybook. If Next.js doesn't support something out of the box, but makes it easy to configure, then this framework will do the same for that thing for Storybook. Any Webpack modifications desired for Storybook should be made in [`.storybook/main.js|ts`](https://storybook.js.org/docs/builders/webpack.md#override-the-default-configuration). Note: Not all Webpack modifications are copy/paste-able between `next.config.js` and `.storybook/main.js|ts`. It is recommended to do your research on how to properly make your modification to Storybook's Webpack config and on how [Webpack works](https://webpack.js.org/concepts/). Below is an example of how to add SVGR support to Storybook with this framework. ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { // ... webpackFinal: async (config) => { config.module = config.module || {}; config.module.rules = config.module.rules || []; // This modifies the existing image rule to exclude .svg files // since you want to handle those files with @svgr/webpack const imageRule = config.module.rules.find((rule) => rule?.['test']?.test('.svg')); if (imageRule) { imageRule['exclude'] = /\.svg$/; } // Configure .svg files to be loaded with @svgr/webpack config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], }); return config; }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... webpackFinal: async (config) => { config.module = config.module || {}; config.module.rules = config.module.rules || []; // This modifies the existing image rule to exclude .svg files // since you want to handle those files with @svgr/webpack const imageRule = config.module.rules.find((rule) => rule?.['test']?.test('.svg')); if (imageRule) { imageRule['exclude'] = /\.svg$/; } // Configure .svg files to be loaded with @svgr/webpack config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], }); return config; }, }); ``` ### Typescript Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box. ```json title="tsconfig.json" { "compilerOptions": { "baseUrl": ".", "paths": { "@/components/*": ["components/*"] } } } ``` ### React Server Components (RSC) (⚠️ **Experimental**) If your app uses [React Server Components (RSC)](https://nextjs.org/docs/app/building-your-application/rendering/server-components), Storybook can render them in stories in the browser. To enable this set the `experimentalRSC` feature flag in your `.storybook/main.js|ts` config: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... features: { experimentalRSC: true, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... features: { experimentalRSC: true, }, }); ``` Setting this flag automatically wraps your story in a [Suspense](https://react.dev/reference/react/Suspense) wrapper, which is able to render asynchronous components in NextJS's version of React. If this wrapper causes problems in any of your existing stories, you can selectively disable it using the `react.rsc` [parameter](https://storybook.js.org/docs/writing-stories/parameters.md) at the global/component/story level: ```ts // MyServerComponent.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const meta = { component: MyServerComponent, parameters: { react: { rsc: false }, }, } satisfies Meta; export default meta; ``` ```ts // MyServerComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: MyServerComponent, parameters: { react: { rsc: false }, }, }); ``` Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To work around this, you'll need to mock out your data access layer using [Webpack aliases](https://webpack.js.org/configuration/resolve/#resolvealias) or an addon like [storybook-addon-module-mock](https://storybook.js.org/addons/storybook-addon-module-mock). If your server components access data via the network, we recommend using the [MSW Storybook Addon](https://storybook.js.org/addons/msw-storybook-addon) to mock network requests. In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). ## Notes for Yarn v2 and v3 users If you're using [Yarn](https://yarnpkg.com/) v2 or v3, you may run into issues where Storybook can't resolve `style-loader` or `css-loader`. For example, you might get errors like: ``` Module not found: Error: Can't resolve 'css-loader' Module not found: Error: Can't resolve 'style-loader' ``` This is because those versions of Yarn have different package resolution rules than Yarn v1.x. If this is the case for you, please install the package directly. ## FAQ ### How do I manually install the Next.js framework? First, install the framework: ```shell npm install --save-dev @storybook/nextjs ``` ```shell pnpm add --save-dev @storybook/nextjs ``` ```shell yarn add --dev @storybook/nextjs ``` Then, update your `.storybook/main.js|ts` to change the framework property: ```diff // .storybook/main.ts — CSF 3 - import type { StorybookConfig } from '@storybook/your-previous-framework'; + import type { StorybookConfig } from '@storybook/nextjs'; const config: StorybookConfig = { // ... - framework: '@storybook/react-webpack5', + framework: '@storybook/nextjs', }; export default config; ``` ```diff // .storybook/main.ts — CSF Next 🧪 - import { defineMain } from '@storybook/your-previous-framework/node'; + import { defineMain } from '@storybook/nextjs/node'; export default defineMain({ // ... - framework: '@storybook/react-webpack5', + framework: '@storybook/nextjs', }); ``` Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... addons: [ // ... // 👇 These can both be removed // 'storybook-addon-next', // 'storybook-addon-next-router', ], }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... addons: [ // ... // 👇 These can both be removed // 'storybook-addon-next', // 'storybook-addon-next-router', ], }); ``` ### How do I migrate to the Next.js Vite framework? Please refer to the [migration instructions for `@storybook/nextjs-vite`](https://storybook.js.org/docs/get-started/frameworks/nextjs-vite.md#how-do-i-migrate-from-the-nextjs-webpack-5-addon). ### Stories for pages/components which fetch data Next.js pages can fetch data directly within server components in the `app` directory, which often include module imports that only run in a Node.js environment. This does not (currently) work within Storybook, because if you import from a Next.js page file containing those node module imports in your stories, your Storybook's Webpack will crash because those modules will not run in a browser. To get around this, you can extract the component in your page file into a separate file and import that pure component in your stories. Or, if that's not feasible for some reason, you can [polyfill those modules](https://webpack.js.org/configuration/node/) in your Storybook's [`webpackFinal` configuration](https://storybook.js.org/docs/builders/webpack.md#override-the-default-configuration). **Before** ```jsx title="app/my-page/index.jsx" async function getData() { const res = await fetch(...); // ... } // Using this component in your stories will break the Storybook build export default async function Page() { const data = await getData(); return // ... } ``` **After** ```jsx title="app/my-page/index.jsx" // Use this component in your stories async function getData() { const res = await fetch(...); // ... } export default async function Page() { const data = await getData(); return ; } ``` ### Statically imported images won't load Make sure you are treating image imports the same way you treat them when using `next/image` in normal development. Before using this framework, image imports would import the raw path to the image (e.g. `'static/media/stories/assets/logo.svg'`). Now image imports work the "Next.js way", meaning that you now get an object when importing an image. For example: ```jsonc // Image import object { "src": "static/media/stories/assets/logo.svg", "height": 48, "width": 48, "blurDataURL": "static/media/stories/assets/logo.svg", } ``` Therefore, if something in Storybook isn't showing the image properly, make sure you expect the object to be returned from an import instead of only the asset path. See [local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) for more detail on how Next.js treats static image imports. ### Module not found: Error: Can't resolve `package name` You might get this if you're using Yarn v2 or v3. See [Notes for Yarn v2 and v3 users](#notes-for-yarn-v2-and-v3-users) for more details. ### Should I use the Vite or Webpack version? We recommend using [`@storybook/nextjs-vite`](https://storybook.js.org/docs/get-started/frameworks/nextjs-vite.md) (Vite-based) for most projects because it offers faster builds, better test support, and a simpler configuration. However, if your project has custom Webpack configurations that are incompatible with Vite, use this framework instead. ### Error: You are importing avif images, but you don't have sharp installed. You have to install sharp in order to use image optimization features in Next.js. `sharp` is a dependency of Next.js's image optimization feature. If you see this error, you need to install `sharp` in your project. ```bash npm install sharp ``` ```bash yarn add sharp ``` ```bash pnpm add sharp ``` You can refer to the [Install `sharp` to Use Built-In Image Optimization](https://nextjs.org/docs/messages/install-sharp) in the Next.js documentation for more information. ## API ### Modules The `@storybook/nextjs` package exports several modules that enable you to [mock](#mocking-modules) Next.js's internal behavior. #### `@storybook/nextjs/export-mocks` Type: `{ getPackageAliases: ({ useESM?: boolean }) => void }` `getPackageAliases` is a helper for generating the aliases needed to set up [portable stories](#portable-stories). ```ts title="jest.config.ts" // 👇 Import the utility function const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: './', }); const config: Config = { testEnvironment: 'jsdom', // ... rest of Jest config moduleNameMapper: { ...getPackageAliases(), // 👈 Add the utility as mapped module names }, }; export default createJestConfig(config); ``` #### `@storybook/nextjs/cache.mock` Type: `typeof import('next/cache')` This module exports mocked implementations of the `next/cache` module's exports. You can use it to create your own mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const Submitted: Story = { async play({ canvas, userEvent }) { const submitButton = canvas.getByRole('button', { name: /submit/i }); await userEvent.click(saveButton); // 👇 Use any mock assertions on the function await expect(revalidatePath).toHaveBeenCalledWith('/'); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const Submitted = meta.story({ async play({ canvas, userEvent }) { const submitButton = canvas.getByRole('button', { name: /submit/i }); await userEvent.click(saveButton); // 👇 Use any mock assertions on the function await expect(revalidatePath).toHaveBeenCalledWith('/'); }, }); ``` #### `@storybook/nextjs/headers.mock` Type: [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options), [`headers`](https://nextjs.org/docs/app/api-reference/functions/headers) and [`draftMode`](https://nextjs.org/docs/app/api-reference/functions/draft-mode) from Next.js This module exports _writable_ mocked implementations of the `next/headers` module's exports. You can use it to set up cookies or headers that are read in your story, and to later assert that they have been called. Next.js's default [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) export is read-only, but this module exposes methods allowing you to write to the headers: - **`headers().append(name: string, value: string)`**: Appends the value to the header if it exists already. - **`headers().delete(name: string)`**: Deletes the header - **`headers().set(name: string, value: string)`**: Sets the header to the value provided. For cookies, you can use the existing API to write them. E.g., `cookies().set('firstName', 'Jane')`. Because `headers()`, `cookies()` and their sub-functions are all mocks you can use any [mock utilities](https://vitest.dev/api/mock.html) in your stories, like `headers().getAll.mock.calls`. ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const LoggedInEurope: Story = { async beforeEach() { // 👇 Set mock cookies, headers and draft mode ahead of rendering cookies().set('username', 'Sol'); headers().set('timezone', 'Central European Summer Time'); draftMode.mockReturnValue({ isEnabled: true, enable: fn(), disable: fn(), }); }, async play() { // 👇 Assert that your component called the mocks await expect(cookies().get).toHaveBeenCalledOnce(); await expect(cookies().get).toHaveBeenCalledWith('username'); await expect(headers().get).toHaveBeenCalledOnce(); await expect(headers().get).toHaveBeenCalledWith('timezone'); await expect(draftMode).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const LoggedInEurope = meta.story({ async beforeEach() { // 👇 Set mock cookies, headers and draft mode ahead of rendering cookies().set('username', 'Sol'); headers().set('timezone', 'Central European Summer Time'); draftMode.mockReturnValue({ isEnabled: true, enable: fn(), disable: fn(), }); }, async play() { // 👇 Assert that your component called the mocks await expect(cookies().get).toHaveBeenCalledOnce(); await expect(cookies().get).toHaveBeenCalledWith('username'); await expect(headers().get).toHaveBeenCalledOnce(); await expect(headers().get).toHaveBeenCalledWith('timezone'); await expect(draftMode).toHaveBeenCalled(); }, }); ``` #### `@storybook/nextjs/navigation.mock` Type: `typeof import('next/navigation') & getRouter: () => ReturnType` This module exports mocked implementations of the `next/navigation` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, parameters: { nextjs: { // 👇 As in the Next.js application, next/navigation only works using App Router appDirectory: true, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const Unauthenticated: Story = { async play() { // 👇 Assert that your component called redirect() await expect(redirect).toHaveBeenCalledWith('/login', 'replace'); }, }; export const GoBack: Story = { async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, parameters: { nextjs: { // 👇 As in the Next.js application, next/navigation only works using App Router appDirectory: true, }, }, }); export const Unauthenticated = meta.story({ async play() { // 👇 Assert that your component called redirect() await expect(redirect).toHaveBeenCalledWith('/login', 'replace'); }, }); export const GoBack = meta.story({ async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }); ``` #### `@storybook/nextjs/router.mock` Type: `typeof import('next/router') & getRouter: () => ReturnType` This module exports mocked implementations of the `next/router` module's exports. It also exports a `getRouter` function that returns a mocked version of [Next.js's `router` object from `useRouter`](https://nextjs.org/docs/pages/api-reference/functions/use-router#router-object), allowing the properties to be manipulated and asserted on. You can use it mock implementations or assert on mock calls in a story's [play function](https://storybook.js.org/docs/writing-stories/play-function.md). ```ts // MyForm.stories.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite // 👇 Must include the `.mock` portion of filename to have mocks typed correctly const meta = { component: MyForm, } satisfies Meta; export default meta; type Story = StoryObj; export const GoBack: Story = { async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }; ``` ```ts // MyForm.stories.ts — CSF Next 🧪 /* * Replace your-framework with nextjs or nextjs-vite * 👇 Must include the `.mock` portion of filename to have mocks typed correctly */ const meta = preview.meta({ component: MyForm, }); export const GoBack = meta.story({ async play({ canvas, userEvent }) { const backBtn = await canvas.findByText('Go back'); await userEvent.click(backBtn); // 👇 Assert that your component called back() await expect(getRouter().back).toHaveBeenCalled(); }, }); ``` ### Options You can pass an options object for additional configuration if needed: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with nextjs or nextjs-vite const config: StorybookConfig = { // ... framework: { name: '@storybook/your-framework', options: { nextConfigPath: path.resolve(process.cwd(), 'next.config.js'), }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with nextjs or nextjs-vite export default defineMain({ // ... framework: { name: '@storybook/your-framework', options: { nextConfigPath: path.resolve(process.cwd(), 'next.config.js'), }, }, }); ``` The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For Next.js, available options can be found in the [Webpack builder docs](https://storybook.js.org/docs/builders/webpack.md). #### `nextConfigPath` Type: `string` The absolute path to the `next.config.js` file. This is necessary if you have a custom `next.config.js` file that is not in the root directory of your project. ### Parameters This framework contributes the following [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) to Storybook, under the `nextjs` namespace: #### `image` Type: `object` Props to pass to every instance of `next/image`. See [next/image docs](https://nextjs.org/docs/pages/api-reference/components/image) for more details. #### `appDirectory` Type: `boolean` Default: `false` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true`. Because this is a parameter, you can apply it to a [single story](https://storybook.js.org/docs/api/parameters.md#story-parameters), [all stories for a component](https://storybook.js.org/docs/api/parameters.md#meta-parameters), or [every story in your Storybook](https://storybook.js.org/docs/api/parameters.md#project-parameters). See [Next.js Navigation](#nextjs-navigation) for more details. #### `navigation` Type: ```ts { asPath?: string; pathname?: string; query?: Record; segments?: (string | [string, string])[]; } ``` Default value: ```js { segments: []; } ``` The router object that is passed to the `next/navigation` context. See [Next.js's navigation docs](https://nextjs.org/docs/app/building-your-application/routing) for more details. #### `router` Type: ```ts { asPath?: string; pathname?: string; query?: Record; } ``` The router object that is passed to the `next/router` context. See [Next.js's router docs](https://nextjs.org/docs/pages/building-your-application/routing) for more details. > Source: https://storybook.js.org/docs/get-started/frameworks/nextjs --- # Storybook for Preact with Vite Storybook for Preact & Vite is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Preact](https://preactjs.com/) applications built with [Vite](https://vitejs.dev/). ## Install To install Storybook in an existing Preact project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## FAQ ### How do I manually install the Preact framework? First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: ## API ### Options You can pass an options object for additional configuration if needed: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). > Source: https://storybook.js.org/docs/get-started/frameworks/preact-vite --- # Storybook for React with Vite Storybook for React & Vite is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Vite](https://vitejs.dev/). ## Install To install Storybook in an existing React project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## FAQ ### How do I migrate from the React Webpack framework? The `upgrade` command should prompt you to migrate to `@storybook/react-vite` when you run it: ```shell npx storybook@latest upgrade ``` ```shell pnpm dlx storybook@latest upgrade ``` ```shell yarn dlx storybook@latest upgrade ``` In case that auto-migration does not work for your project, refer to the manual installation instructions below. ### How do I manually install the React framework? First, install the framework: ```shell npm install --save-dev @storybook/react-vite ``` ```shell pnpm add --save-dev @storybook/react-vite ``` ```shell yarn add --dev @storybook/react-vite ``` Then, update your `.storybook/main.js|ts` to change the framework property: ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { // ... // framework: '@storybook/react-webpack5', 👈 Remove this framework: '@storybook/react-vite', // 👈 Add this }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ // ... // framework: '@storybook/react-webpack5', 👈 Remove this framework: '@storybook/react-vite', // 👈 Add this }); ``` ## API ### Options You can pass an options object for additional configuration if needed: ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { framework: { name: '@storybook/react-vite', options: { // ... }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: { name: '@storybook/react-vite', options: { // ... }, }, }); ``` #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). > Source: https://storybook.js.org/docs/get-started/frameworks/react-vite --- # Storybook for React with Webpack Storybook for React & Webpack is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Webpack](https://webpack.js.org/). **We recommend using [`@storybook/react-vite`](https://storybook.js.org/docs/get-started/frameworks/react-vite.md)** for most React projects. The Vite-based framework is faster, more modern, and offers better support for testing features. Use this Webpack-based framework (`@storybook/react-webpack5`) only if you need specific Webpack features not available in Vite. ## Install To install Storybook in an existing React project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure ### Create React App (CRA) Support for [Create React App](https://create-react-app.dev/) is handled by [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app). This preset enables support for all CRA features, including Sass/SCSS and TypeScript. ### Manually initialized apps If you're working on an app that was initialized manually (i.e., without the use of CRA), ensure that your app has [react-dom](https://www.npmjs.com/package/react-dom) included as a dependency. Failing to do so can lead to unforeseen issues with Storybook and your project. ## FAQ ### How do I manually install the React Webpack framework? First, install the framework: ```shell npm install --save-dev @storybook/react-webpack5 ``` ```shell pnpm add --save-dev @storybook/react-webpack5 ``` ```shell yarn add --dev @storybook/react-webpack5 ``` Next, install and register your appropriate compiler addon, depending on whether you're using SWC (recommended) or Babel: If your project is using [Create React App](#create-react-app-cra), you can skip this step. ```sh npx storybook@latest add @storybook/addon-webpack5-compiler-swc ``` ```sh pnpm dlx storybook@latest add @storybook/addon-webpack5-compiler-swc ``` ```sh yarn dlx storybook@latest add @storybook/addon-webpack5-compiler-swc ``` or ```sh npx storybook@latest add @storybook/addon-webpack5-compiler-babel ``` ```sh pnpm dlx storybook@latest add @storybook/addon-webpack5-compiler-babel ``` ```sh yarn dlx storybook@latest add @storybook/addon-webpack5-compiler-babel ``` More details can be found in the [Webpack builder docs](https://storybook.js.org/docs/builders/webpack.md#compiler-support). Finally, update your `.storybook/main.js|ts` to change the framework property: ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { // ... framework: '@storybook/react-webpack5', // 👈 Add this }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ // ... framework: '@storybook/react-webpack5', // 👈 Add this }); ``` ### How do I migrate to the React Vite framework? Please refer to the [migration instructions for `@storybook/react-vite`](https://storybook.js.org/docs/get-started/frameworks/react-vite.md#how-do-i-migrate-from-the-react-webpack-framework). ## API ### Options You can pass an options object for additional configuration if needed: ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { framework: { name: '@storybook/react-webpack5', options: { // ... }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: { name: '@storybook/react-webpack5', options: { // ... }, }, }); ``` #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Webpack builder docs](https://storybook.js.org/docs/builders/webpack.md). > Source: https://storybook.js.org/docs/get-started/frameworks/react-webpack5 --- # Storybook for React Native Web Storybook for React Native Web is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [React Native](https://reactnative.dev/). It uses [Vite](https://vitejs.dev/) to build your components for web browsers. In addition to React Native Web, Storybook supports on-device [React Native](https://github.com/storybookjs/react-native) development. If you're unsure what's right for you, read our [comparison](#react-native-vs-react-native-web). ## Install To install Storybook in an existing React Native project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## React Native vs React Native Web If you’re building React Native (RN) components, Storybook has two options: Native and Web. Both options provide a catalog of your stories that hot refreshes as you edit the code in your favorite editor. However, their implementations are quite different: - **Native** - Runs inside your React Native application. It’s high-fidelity but has a limited feature set. - **Web** - Displays your React Native components in the browser. It’s based on Storybook for Web, which is feature-rich and mature. ### Comparison So, which option is right for you? **Native.** You should choose this option if you want: - **Native features** - Your components rely on device-specific features like native modules. It runs in your actual application, in-simulator, or on-device and provides full fidelity. The web version uses `react-native-web`, which works for most components but has [limitations](https://necolas.github.io/react-native-web/docs/react-native-compatibility/). - **Mobile publication** - You want to share your Storybook on-device as part of a test build or embedded inside your application. **Web.** You should choose this option if you want: - [**Sharing**](https://storybook.js.org/docs/sharing/publish-storybook.md) - Publish to the web and share with your team or publicly. - [**Documentation**](https://storybook.js.org/docs/writing-docs.md) - Auto-generated component docs or rich markdown docs in MDX. - [**Testing**](https://storybook.js.org/docs/writing-tests.md) - Component, visual, and a11y tests for your components. - [**Addons**](https://storybook.js.org/addons) - 500+ addons that improve development, documentation, testing, and integration with other tools. **Both.** It’s also possible to use both options together. This increases Storybook’s install footprint but is a good option if you want native fidelity in addition to all of the web features. Learn more below. ### Using both React Native and React Native Web The easiest way to use React Native and React Native Web is to select the **"Both"** option when installing Storybook. This will install and create configurations for both environments, allowing you to run Storybook for both in the same project. When you select "Both", the installation will: 1. Install and configure React Native Storybook (on-device) 2. Install and configure React Native Web Storybook (web-based) 3. Set up both configurations in your project After installation, you'll see instructions for both environments: - React Native Storybook will require additional manual configuration steps (replacing app entry, wrapping metro config) - React Native Web Storybook can be started immediately using the `storybook` command However, you can install them separately if one version is installed. You can add a React Native Web Storybook alongside an existing React Native Storybook by running the install command and selecting "React Native Web" in the setup wizard, and vice versa. ## FAQ ### How do I migrate from the React Native Web addon? The [React Native Web addon](https://github.com/storybookjs/addon-react-native-web) was a Webpack-based precursor to the React Native Web Vite framework (i.e., `@storybook/react-native-web-vite`). If you're using the addon, you should migrate to the framework, which is faster, more stable, maintained, and better documented. To do so, follow the steps below. Run the following command to upgrade Storybook to the latest version: ```shell npx storybook@latest upgrade ``` ```shell pnpm dlx storybook@latest upgrade ``` ```shell yarn dlx storybook@latest upgrade ``` This framework is designed to work with Storybook 8.5 and above for the best experience. We won't be able to provide support if you're using an older Storybook version. Install the framework and its peer dependencies: ```shell npm install --save-dev @storybook/react-native-web-vite vite ``` ```shell pnpm add --save-dev @storybook/react-native-web-vite vite ``` ```shell yarn add --dev @storybook/react-native-web-vite vite ``` Update your `.storybook/main.js|ts` to change the framework property and remove the `@storybook/addon-react-native-web` addon: ```ts // .storybook/main.ts const config: StorybookConfig = { addons: [ '@storybook/addon-react-native-web', // 👈 Remove the addon ], // Replace @storybook/react-webpack5 with the Vite framework framework: '@storybook/react-native-web-vite', }; export default config; ``` Finally, remove the addon and similar packages (i.e., `@storybook/react-webpack5` and `@storybook/addon-react-native-web`) from your project. ## API ### Options You can pass an options object for additional configuration if needed: ```ts title=".storybook/main.ts" const config: StorybookConfig = { framework: { name: '@storybook/react-native-web-vite', options: { modulesToTranspile: ['my-library'], // add libraries that are not transpiled for web by default // You should apply babel plugins and presets here for your project that you want to apply to your code // for example put the reanimated preset here if you are using reanimated // or the nativewind jsxImportSource for example pluginReactOptions: { jsxRuntime: 'automatic' | 'classic', // default: 'automatic' jsxImportSource: string, // default: 'react' babel:{ plugins: Array, presets: Array, // ... other compatible babel options } include: Array, exclude: Array, // ... other compatible @vitejs/plugin-react options } }, }, }; export default config; ``` #### Example configuration for reanimated ```ts title=".storybook/main.ts" const config: StorybookConfig = { // ... rest of config framework: { name: '@storybook/react-native-web-vite', options: { pluginReactOptions: { babel: { plugins: [ '@babel/plugin-proposal-export-namespace-from', 'react-native-reanimated/plugin', ], }, }, }, }, // ... rest of config }; ``` #### Example configuration for nativewind ```ts title=".storybook/main.ts" const config: StorybookConfig = { // ... rest of config framework: { name: '@storybook/react-native-web-vite', options: { pluginReactOptions: { jsxImportSource: 'nativewind', }, }, }, }; ``` #### Example configuration to transpile additional node_modules Let's say you need to transpile a library called `my-library` that is not transpiled for web by default. You can add it to the `modulesToTranspile` option. ```ts title=".storybook/main.ts" const config: StorybookConfig = { // ... rest of config framework: { name: '@storybook/react-native-web-vite', options: { modulesToTranspile: ['my-library'], }, }, }; ``` #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). > Source: https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite --- # Storybook for SvelteKit Storybook for SvelteKit is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [SvelteKit](https://kit.svelte.dev/) applications. ## Install To install Storybook in an existing SvelteKit project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure This section covers SvelteKit support and configuration options. ### Supported features All Svelte language features are supported out of the box, as the Storybook framework uses the Svelte compiler directly. However, SvelteKit has some [Kit-specific modules](https://kit.svelte.dev/docs/modules) that aren't supported. Here's a breakdown of what will and will not work within Storybook: | Module | Status | Note | | -------------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | [`$app/environment`](https://svelte.dev/docs/kit/$app-environment) | ✅ Supported | `version` is always empty in Storybook. | | [`$app/forms`](https://svelte.dev/docs/kit/$app-forms) | ⚠️ **Experimental** | See [How to mock](#how-to-mock). | | [`$app/navigation`](https://svelte.dev/docs/kit/$app-navigation) | ⚠️ **Experimental** | See [How to mock](#how-to-mock). | | [`$app/paths`](https://svelte.dev/docs/kit/$app-paths) | ✅ Supported | Requires SvelteKit 1.4.0 or newer. | | [`$app/state`](https://svelte.dev/docs/kit/$app-state) | ⚠️ **Experimental** | Requires SvelteKit `v2.12` or newer. See [How to mock](#how-to-mock). | | [`$app/stores`](https://svelte.dev/docs/kit/$app-stores) | ⚠️ **Experimental** | See [How to mock](#how-to-mock). | | [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public) | 🚧 Partially supported | Only supported in development mode. Storybook is built as a static app with no server-side API, so it cannot dynamically serve content. | | [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public) | ✅ Supported | | | [`$lib`](https://svelte.dev/docs/kit/$lib) | ✅ Supported | | | [`@sveltejs/kit/*`](https://svelte.dev/docs/kit/@sveltejs-kit) | ✅ Supported | | | [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private) | ⛔ Not supported | This is a server-side feature, and Storybook renders all components on the client. | | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | ⛔ Not supported | This is a server-side feature, and Storybook renders all components on the client. | | [`$service-worker`](https://svelte.dev/docs/kit/$service-worker) | ⛔ Not supported | This is a service worker feature, which does not apply to Storybook. | ### How to mock To mock a SvelteKit import you can define it within `parameters.sveltekit_experimental`: The [available parameters](#parameters) are documented in the API section, below. #### Mocking links The default link-handling behavior (e.g., when clicking an `` element) is to log an action to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). You can override this by assigning an object to `parameters.sveltekit_experimental.hrefs`, where the keys are strings representing an href, and the values define your mock. For example: See the [API reference](#hrefs) for more information. ## Writing native Svelte stories Storybook provides a Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) maintained by the community, enabling you to write stories for your Svelte components using the template syntax. The community actively maintains the Svelte CSF addon but still lacks some features currently available in the official Storybook Svelte framework support. For more information, see the [addon's documentation](https://github.com/storybookjs/addon-svelte-csf). ### Setup If you initialized your project with the Sveltekit framework, the addon has already been installed and configured for you. However, if you're [migrating](#automatic-migration) from a previous version, you'll need to take additional steps to enable this feature. Run the following command to install the addon. The CLI's [`add`](https://storybook.js.org/docs/api/cli-options.md#add) command automates the addon's installation and setup. To install it manually, see our [documentation](https://storybook.js.org/docs/addons/install-addons.md#manual-installation) on how to install addons. Update your Storybook configuration file (i.e., `.storybook/main.js|ts`) to enable support for this format. ### Configure By default, the Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) addon offers zero-config support for Storybook's SvelteKit framework. However, you can extend your Storybook configuration file (i.e., `.storybook/main.js|ts`) and provide additional addon options. Listed below are the available options and examples of how to use them. | Options | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | `legacyTemplate` | Enables support for the `Template` component for backward compatibility.
`options: { legacyTemplate: true }` | Enabling the `legacyTemplate` option can introduce a performance overhead and should be used cautiously. For more information, refer to the [addon's documentation](https://github.com/storybookjs/addon-svelte-csf/blob/next/README.md#legacy-api). ### Upgrade to Svelte CSF addon v5 With the Svelte 5 release, Storybook's Svelte CSF addon has been updated to support the new features. This guide will help you migrate to the latest version of the addon. Below is an overview of the major changes in version 5.0 and the steps needed to upgrade your project. #### Simplified story API If you are using the `Meta` component or the `meta` named export to define the story's metadata (e.g., [parameters](https://storybook.js.org/docs/writing-stories/parameters.md)), you'll need to update your stories to use the new `defineMeta` function. This function returns an object with the required information, including a `Story` component that you must use to define your component stories. #### Story templates If you used the `Template` component to control how the component renders in the Storybook, this feature was replaced with built-in children support in the `Story` component, enabling you to compose components and define the UI structure directly in the story. If you need support for the `Template` component, the addon provides a feature flag for backward compatibility. For more information, see the [configuration options](#configure). #### Story slots to snippets With Svelte's slot deprecation and the introduction of reusable [`snippets`](https://svelte.dev/docs/svelte/v5-migration-guide#Snippets-instead-of-slots), the addon also introduced support for this feature allowing you to extend the `Story` component and provide a custom snippet to provide dynamic content to your stories. `Story` accepts a `template` snippet, allowing you to create dynamic stories without losing reactivity. ```svelte title="MyComponent.stories.svelte" {#snippet template(args)} Reactive component {/snippet} ``` #### Tags support If you enabled automatic documentation generation with the `autodocs` story property, you must replace it with [`tags`](https://storybook.js.org/docs/writing-stories/tags.md). This property allows you to categorize and filter stories based on specific criteria and generate documentation based on the tags applied to the stories. ## FAQ ### How do I manually install the SvelteKit framework? First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: Finally, these packages are now either obsolete or part of `@storybook/sveltekit`, so you no longer need to depend on them directly. You can remove them (`npm uninstall`, `yarn remove`, `pnpm remove`) from your project: - `@storybook/svelte-vite` - `storybook-builder-vite` - `@storybook/builder-vite` ## API ### Parameters This framework contributes the following [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) to Storybook, under the `sveltekit_experimental` namespace: #### `forms` Type: `{ enhance: () => void }` Provides mocks for the [`$app/forms`](https://svelte.dev/docs/kit/$app-forms) module. ##### `forms.enhance` Type: `() => void` A callback that will be called when a form with [`use:enhance`](https://kit.svelte.dev/docs/form-actions#progressive-enhancement-use-enhance) is submitted. #### `hrefs` Type: `Record<[path: string], (to: string, event: MouseEvent) => void | { callback: (to: string, event: MouseEvent) => void, asRegex?: boolean }>` If you have an `
` tag inside your code with the `href` attribute that matches one or more of the links defined (treated as regex based if the `asRegex` property is `true`) the corresponding `callback` will be called. If no matching `hrefs` are defined, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). See [Mocking links](#mocking-links) for an example. #### `navigation` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation) Provides mocks for the [`$app/navigation`](https://svelte.dev/docs/kit/$app-navigation) module. ##### `navigation.goto` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#goto) A callback that will be called whenever [`goto`](https://svelte.dev/docs/kit/$app-navigation#goto) is called. If no function is provided, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). ##### `navigation.pushState` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#pushState) A callback that will be called whenever [`pushState`](https://svelte.dev/docs/kit/$app-navigation#pushState) is called. If no function is provided, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). ##### `navigation.replaceState` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#replaceState) A callback that will be called whenever [`replaceState`](https://svelte.dev/docs/kit/$app-navigation#replaceState) is called. If no function is provided, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). ##### `navigation.invalidate` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#invalidate) A callback that will be called whenever [`invalidate`](https://svelte.dev/docs/kit/$app-navigation#invalidate) is called. If no function is provided, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). ##### `navigation.invalidateAll` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#invalidateAll) A callback that will be called whenever [`invalidateAll`](https://svelte.dev/docs/kit/$app-navigation#invalidateAll) is called. If no function is provided, an action will be logged to the [Actions panel](https://storybook.js.org/docs/essentials/actions.md). ##### `navigation.afterNavigate` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-navigation#afterNavigate) An object that will be passed to the [`afterNavigate`](https://svelte.dev/docs/kit/$app-navigation#afterNavigate) function, which will be invoked when the `onMount` event fires. #### `stores` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-stores) Provides mocks for the [`$app/stores`](https://svelte.dev/docs/kit/$app-stores) module. ##### `stores.navigating` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-stores#navigating) A partial version of the [`navigating`](https://svelte.dev/docs/kit/$app-stores#navigating) store. ##### `stores.page` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-stores#page) A partial version of the [`page`](https://svelte.dev/docs/kit/$app-stores#page) store. ##### `stores.updated` Type: boolean A boolean representing the value of [`updated`](https://svelte.dev/docs/kit/$app-stores#updated) (you can also access `updated.check()` which will be a no-op). #### `state` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-state) Provides mocks for the [`$app/state`](https://svelte.dev/docs/kit/$app-state) module. ##### `state.navigating` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-state#navigating) A partial version of the [`navigating`](https://svelte.dev/docs/kit/$app-state#navigating) store. ##### `state.page` Type: See [SvelteKit docs](https://svelte.dev/docs/kit/$app-state#page) A partial version of the [`page`](https://svelte.dev/docs/kit/$app-state#page) store. ##### `state.updated` Type: `{ current: boolean }` An object representing the current value of [`updated`](https://svelte.dev/docs/kit/$app-state#updated). You can also access `updated.check()`, which will be a no-op. ### Options You can pass an options object for additional configuration if needed: The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For Sveltekit, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). #### `docgen` Type: `boolean` Default: `true` Enables or disables automatic documentation generation for component properties. When disabled, Storybook will skip the docgen processing step during build, which can improve build performance. #### When to disable docgen Disabling docgen can improve build performance for large projects, but [argTypes won't be inferred automatically](https://storybook.js.org/docs/api/arg-types.md#automatic-argtype-inference), which will prevent features like [Controls](https://storybook.js.org/docs/essentials/controls.md) and [docs](https://storybook.js.org/docs/writing-docs/autodocs.md) from working as expected. To use those features, you will need to [define `argTypes` manually](https://storybook.js.org/docs/api/arg-types.md#manually-specifying-argtypes). > Source: https://storybook.js.org/docs/get-started/frameworks/sveltekit --- # Storybook for Svelte with Vite Storybook for Svelte & Vite is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for applications using [Svelte](https://svelte.dev/) built with [Vite](https://vitejs.dev/). ## Install To install Storybook in an existing Svelte project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Writing native Svelte stories Storybook provides a Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) maintained by the community, enabling you to write stories for your Svelte components using the template syntax. The community actively maintains the Svelte CSF addon but still lacks some features currently available in the official Storybook Svelte framework support. For more information, see the [addon's documentation](https://github.com/storybookjs/addon-svelte-csf). ### Setup If you initialized your project with the Svelte framework, the addon has already been installed and configured for you. However, if you're [migrating](#automatic-migration) from a previous version, you'll need to take additional steps to enable this feature. Run the following command to install the addon. The CLI's [`add`](https://storybook.js.org/docs/api/cli-options.md#add) command automates the addon's installation and setup. To install it manually, see our [documentation](https://storybook.js.org/docs/addons/install-addons.md#manual-installation) on how to install addons. Update your Storybook configuration file (i.e., `.storybook/main.js|ts`) to enable support for this format. ### Configure By default, the Svelte [addon](https://storybook.js.org/addons/@storybook/addon-svelte-csf) offers zero-config support for Storybook's Svelte framework. However, you can extend your Storybook configuration file (i.e., `.storybook/main.js|ts`) and provide additional addon options. Listed below are the available options and examples of how to use them. | Options | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | `legacyTemplate` | Enables support for the `Template` component for backward compatibility.
`options: { legacyTemplate: true }` | Enabling the `legacyTemplate` option can introduce a performance overhead and should be used cautiously. For more information, refer to the [addon's documentation](https://github.com/storybookjs/addon-svelte-csf/blob/next/README.md#legacy-api). ### Upgrade to Svelte CSF addon v5 With the Svelte 5 release, Storybook's Svelte CSF addon has been updated to support the new features. This guide will help you migrate to the latest version of the addon. Below is an overview of the major changes in version 5.0 and the steps needed to upgrade your project. #### Simplified story API If you are using the `Meta` component or the `meta` named export to define the story's metadata (e.g., [parameters](https://storybook.js.org/docs/writing-stories/parameters.md)), you'll need to update your stories to use the new `defineMeta` function. This function returns an object with the required information, including a `Story` component that you must use to define your component stories. #### Story templates If you used the `Template` component to control how the component renders in the Storybook, this feature was replaced with built-in children support in the `Story` component, enabling you to compose components and define the UI structure directly in the story. If you need support for the `Template` component, the addon provides a feature flag for backward compatibility. For more information, see the [configuration options](#configure). #### Story slots to snippets With Svelte's slot deprecation and the introduction of reusable [`snippets`](https://svelte.dev/docs/svelte/v5-migration-guide#Snippets-instead-of-slots), the addon also introduced support for this feature allowing you to extend the `Story` component and provide a custom snippet to provide dynamic content to your stories. `Story` accepts a `template` snippet, allowing you to create dynamic stories without losing reactivity. ```svelte title="MyComponent.stories.svelte" {#snippet template(args)} Reactive component {/snippet} ``` #### Tags support If you enabled automatic documentation generation with the `autodocs` story property, you must replace it with [`tags`](https://storybook.js.org/docs/writing-stories/tags.md). This property allows you to categorize and filter stories based on specific criteria and generate documentation based on the tags applied to the stories. ## FAQ ### How do I manually install the Svelte framework? First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: ## API ### Options You can pass an options object for additional configuration if needed: The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). #### `docgen` Type: `boolean` Default: `true` Enables or disables automatic documentation generation for component properties. When disabled, Storybook will skip the docgen processing step during build, which can improve build performance. #### When to disable docgen Disabling docgen can improve build performance for large projects, but [argTypes won't be inferred automatically](https://storybook.js.org/docs/api/arg-types.md#automatic-argtype-inference), which will prevent features like [Controls](https://storybook.js.org/docs/essentials/controls.md) and [docs](https://storybook.js.org/docs/writing-docs/autodocs.md) from working as expected. To use those features, you will need to [define `argTypes` manually](https://storybook.js.org/docs/api/arg-types.md#manually-specifying-argtypes). > Source: https://storybook.js.org/docs/get-started/frameworks/svelte-vite --- # Storybook for TanStack React Storybook for TanStack React is Storybook's [framework](https://storybook.js.org/docs/contribute/framework.md) integration for [TanStack Router](https://tanstack.com/router) and [TanStack Start](https://tanstack.com/start/latest) applications built with [React](https://react.dev/) and [Vite](https://vitejs.dev/). It builds on [`@storybook/react-vite`](https://storybook.js.org/docs/get-started/frameworks/react-vite.md) to add router-aware story rendering, automatic router mocking, and mocked TanStack Start server functions. Components that depend on routing or server functions can render inside Storybook without booting your full app runtime. ## Install To install Storybook in an existing TanStack Router or TanStack Start project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md), and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements This integration expects a TanStack Router application with `@tanstack/react-router` available in your project. If your app uses TanStack Start APIs such as server functions, keep the matching TanStack Start packages installed as well. ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure Storybook for TanStack React uses Vite through [`@storybook/builder-vite`](https://storybook.js.org/docs/builders/vite.md) and automatically wraps each story in a [memory-backed TanStack Router](https://tanstack.com/router/latest/docs/guide/history-types#memory-routing). This gives you a working router context in Storybook without having to boot your full application shell. Out of the box, it supports these workflows: - Supply a TanStack Route to render it as the story component - Set the initial route, params, and query string per story - Override `loader`, `beforeLoad`, and more on the story route without modifying the original route object via `routeOverrides` - Automatically mock `@tanstack/react-router` so navigation hooks work in stories and navigation attempts can be observed - Automatically stub TanStack Start server and runtime entry points so components using server functions can render in Storybook ### Routing #### Rendering a Route Supply a TanStack Route object via `parameters.tanstack.router.route`. Storybook extracts the route's React component from the route and keeps the route available for typed router configuration. ```ts // Page.stories.ts — CSF 3 const meta = { parameters: { layout: 'fullscreen', tanstack: { router: { route: Route, // 👈 Supply the Route here // 👇 Rest of these properties are type-safe params: { id: '42' }, query: { tab: 'details' }, }, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = {}; export const WithCustomLoader: Story = { parameters: { tanstack: { router: { route: Route, // 👈 Supply the Route here // 👇 Rest of these properties are type-safe params: { id: '42' }, routeOverrides: { '/items/$id': { loader: async () => ({ item: { id: '42', name: 'Loaded inside Storybook' }, }), }, }, }, }, }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ parameters: { layout: 'fullscreen', tanstack: { router: { route: Route, // 👈 Supply the Route here // 👇 Rest of these properties are type-safe params: { id: '42' }, query: { tab: 'details' }, }, }, }, }); export const Default = meta.story(); export const WithCustomLoader = meta.story({ parameters: { tanstack: { router: { route: Route, // 👈 Supply the Route here // 👇 Rest of these properties are type-safe params: { id: '42' }, routeOverrides: { '/items/$id': { loader: async () => ({ item: { id: '42', name: 'Loaded inside Storybook' }, }), }, }, }, }, }, }); ``` ##### Handling dynamic params (e.g., `/$id`) Supply `params` alongside `routeOverrides` under `parameters.tanstack.router`. The `params` object is interpolated into the URL, and `routeOverrides` lets you stub the loader without touching the original route. ```ts // Showcase.stories.ts — CSF 3 const meta = { parameters: { tanstack: { router: { route: Route, params: { id: '42' }, routeOverrides: { '/showcase/$id': { loader: () => ({ item: mockItem }), }, }, }, }, }, } satisfies Meta; export default meta; ``` ```ts // Showcase.stories.ts — CSF Next 🧪 const meta = preview.meta({ parameters: { tanstack: { router: { route: Route, params: { id: '42' }, routeOverrides: { '/showcase/$id': { loader: () => ({ item: mockItem }), }, }, }, }, }, }); ``` For the full set of properties, see [`Parameters`](#parameters). #### Rendering nested routes When `route` is a file route connected to your app's route tree, Storybook automatically includes parent layout routes so the story renders inside the same nested hierarchy as the real app. You can also pass the `routeTree` export from `routeTree.gen.ts` directly. Use `path` to navigate to the specific route, and `routeOverrides` to stub guards or loaders on ancestor routes so the story can render independently. ```ts // SettingsProfile.stories.ts — CSF 3 // 👇 Route file is part of the app route tree const meta = { parameters: { tanstack: { router: { // 👇 Storybook walks up the tree to root and duplicates the full route tree, // so parent layouts (e.g. the authenticated shell) also render. route: Route, path: '/settings/profile', // 👇 Stub out any parent route guards so the story can render standalone. routeOverrides: { '/_authenticated': { beforeLoad: () => {} }, }, }, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = {}; ``` ```ts // SettingsProfile.stories.ts — CSF Next 🧪 // 👇 Route file is part of the app route tree const meta = preview.meta({ parameters: { tanstack: { router: { // 👇 Storybook walks up the tree to root and duplicates the full route tree, // so parent layouts (e.g. the authenticated shell) also render. route: Route, path: '/settings/profile', // 👇 Stub out any parent route guards so the story can render standalone. routeOverrides: { '/_authenticated': { beforeLoad: () => {} }, }, }, }, }, }); export const Default = meta.story(); ``` #### Using router parameters with a non-Route component If your story renders a regular React component instead of a route object, you can still provide routing context through `parameters.tanstack.router`. ```ts // Page.stories.ts — CSF 3 const meta = { component: Page, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { parameters: { tanstack: { router: { route: { path: '/demo/form/address', }, query: { view: 'list' }, }, }, }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Page, }); export const Default = meta.story({ parameters: { tanstack: { router: { route: { path: '/demo/form/address', }, query: { view: 'list' }, }, }, }, }); ``` This is useful when your component reads from hooks such as `useRouterState`, `useSearch`, `useParams`, or `useLoaderData`, but you do not want to make the route itself the story component. #### Defining search params and URL fragments (hashes) Use `query` for search params (e.g., `?tab=details&page=2`) and `path` for a URL fragment (e.g., `#section-name`) under `parameters.tanstack.router`: ```ts // Page.stories.ts — CSF 3 const meta = { parameters: { tanstack: { router: { route: Route, }, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const WithHash: Story = { parameters: { tanstack: { // 👇 Provide the URL fragment (hash) for the route router: { path: '/#section-name' }, }, }, }; export const WithSearch: Story = { parameters: { tanstack: { // 👇 Provide the query string for the route router: { query: { tab: 'details', page: '2' } }, }, }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ parameters: { tanstack: { router: { route: Route, }, }, }, }); export const WithHash = meta.story({ parameters: { tanstack: { // 👇 Provide the URL fragment (hash) for the route router: { path: '/#section-name' }, }, }, }); export const WithSearch = meta.story({ parameters: { tanstack: { // 👇 Provide the query string for the route router: { query: { tab: 'details', page: '2' } }, }, }, }); ``` #### Overriding route options per story When a route has a [`loader`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#loader-method) or [`beforeLoad`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#beforeload-method) that calls real APIs, you can override those options per story without modifying the original route object. Pass `routeOverrides` under `parameters.tanstack.router`. Each key is a route ID and the value can override [`loader`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#loader-method), [`beforeLoad`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#beforeload-method), [`validateSearch`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#validatesearch-method), [`loaderDeps`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#loaderdeps-method), and [`context`](https://tanstack.com/router/latest/docs/guide/router-context). Use `'__root__'` as the key to target the root route. ```ts // UserCard.stories.ts — CSF 3 const meta = { title: 'Users/UserCard', parameters: { tanstack: { router: { route: Route, params: { userId: '42' }, // 👇 Override the route's loader so the story doesn't call the real API. routeOverrides: { '/users/$userId': { loader: async () => ({ user: { id: '42', name: 'Ada Lovelace' } }), }, }, }, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = {}; ``` ```ts // UserCard.stories.ts — CSF Next 🧪 const meta = preview.meta({ title: 'Users/UserCard', parameters: { tanstack: { router: { route: Route, params: { userId: '42' }, // 👇 Override the route's loader so the story doesn't call the real API. routeOverrides: { '/users/$userId': { loader: async () => ({ user: { id: '42', name: 'Ada Lovelace' } }), }, }, }, }, }, }); export const Default = meta.story(); ``` ### Mocking #### Automatic TanStack Router and TanStack Start mocks This framework automatically redirects `@tanstack/react-router` imports to a Storybook-compatible mock layer. That mock re-exports TanStack Router APIs, keeps hooks such as [`useNavigate()`](https://tanstack.com/router/latest/docs/api/router/useNavigateHook), [`useSearch()`](https://tanstack.com/router/latest/docs/api/router/useSearchHook), and [`useParams()`](https://tanstack.com/router/latest/docs/api/router/useParamsHook) available in stories, and wires navigation attempts into Storybook spies. For TanStack Start apps, the integration also stubs TanStack Start server and runtime entry points. This is what allows components that depend on server functions or Start-specific runtime modules to render in Storybook without a running Start server. In practice, this means you can usually render TanStack Start components directly, and `createServerFn()` handlers are replaced with mock functions that you can observe and override in stories and tests. #### Mocking server functions in stories If your component imports a TanStack Start server function, Storybook turns that [`createServerFn().handler(...)`](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions) result into a mock function. That means you can override it per story with standard mock APIs. For example, imagine your application code exports a server function like this: ```ts // src/lib/updateProfile.ts export const updateProfile = createServerFn({ method: 'POST' }).handler( async ({ data }: { data: { name: string } }) => { return { ok: true, name: data.name }; }, ); ``` In Storybook, you can override that function for each story: ```ts // ProfileForm.stories.ts — CSF 3 const meta = { component: ProfileForm, } satisfies Meta; export default meta; type Story = StoryObj; export const Success: Story = { beforeEach: async () => { mocked(updateProfile).mockResolvedValue({ ok: true, name: 'Ada Lovelace' }); }, play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByLabelText('Name'), 'Ada Lovelace'); await userEvent.click(canvas.getByRole('button', { name: 'Save profile' })); await expect(updateProfile).toHaveBeenCalled(); }, }; export const Failure: Story = { beforeEach: async () => { mocked(updateProfile).mockRejectedValue(new Error('Could not save profile')); }, }; ``` ```ts // ProfileForm.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: ProfileForm, }); export const Success = meta.story({ beforeEach: async () => { mocked(updateProfile).mockResolvedValue({ ok: true, name: 'Ada Lovelace' }); }, play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByLabelText('Name'), 'Ada Lovelace'); await userEvent.click(canvas.getByRole('button', { name: 'Save profile' })); await expect(updateProfile).toHaveBeenCalled(); }, }); export const Failure = meta.story({ beforeEach: async () => { mocked(updateProfile).mockRejectedValue(new Error('Could not save profile')); }, }); ``` This is useful for documenting loading, success, and error states without changing your application code. #### Handling server-only dependencies TanStack Start apps often import server-only packages (e.g. database clients, auth libraries) at module scope inside route files. When Storybook loads the route tree, those imports can crash the browser. The integration handles this at three layers: ##### Framework-level mocks (automatic) The preset already intercepts `@tanstack/react-start`, `@tanstack/react-start/server`, `@tanstack/start-storage-context`, and related TanStack modules. It also replaces `createServerFn()` handlers with mock functions. You do not need to do anything for these. ##### App-level server modules When your routes import app-specific server code (e.g. `~/db/client`, `~/auth/index.server`), use Storybook's mocking with a `__mocks__` file to prevent the real module (and its Node.js dependencies) from loading in the browser. **Step 1**: Register the mock in `.storybook/preview.ts`: ```ts // .storybook/preview.ts — CSF 3 // Prevents postgres (Node-only) from loading in the browser sb.mock(import('../src/db/client.ts')); export default {}; ``` ```ts // .storybook/preview.ts — CSF Next 🧪 // Prevents postgres (Node-only) from loading in the browser sb.mock(import('../src/db/client.ts')); export default definePreview({}); ``` **Step 2**: Create `src/db/__mocks__/client.ts` next to the real module. Use only `import type` so no server packages are pulled in: ```ts // src/db/__mocks__/client.ts export const db = new Proxy({} as ReturnType>, { get: () => () => Promise.resolve([]), }); ``` **Why a `__mocks__` file instead of automocking?** [Storybook's automocking](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md#automocking) replaces _functions_ but still **evaluates the original module** and its imports. For modules that import `postgres`, `pg`, or other Node.js-only packages, the original module must never be evaluated, because it would crash the browser. A `__mocks__` file is the only approach that completely prevents evaluation of the original module and its dependency chain. ##### Identifying what to mock Errors like `does not provide an export named 'default'` or `AsyncLocalStorage is not defined` mean a server-only module reached the browser. The fix is to mock the **server module itself**, not the component or route that uses it. For example, if `Dashboard.tsx` imports `~/auth/session`, and `~/auth/session` imports `~/db/client`, and `~/db/client` imports `postgres` — mock `~/db/client`. The Node.js dependency (`postgres`) is the smoking gun; mock the closest module to it that you control. To find that module, walk the error stack trace from top to bottom and stop at the first import you wrote yourself. Then add a [`__mocks__` file](#app-level-server-modules) for it. Two cases where you do _not_ need a mock: - The module is from `@tanstack/*` — already handled by the framework preset. Make sure you are on the latest `@storybook/tanstack-react`. - The module only imports `createServerFn` — already mocked. The error is coming from another import in the same file. ### TanStack Query You can use this framework together with [TanStack Query](https://tanstack.com/query) to provide a working QueryClient in Storybook and seed query data per story. #### Project setup TanStack Query is not automatically set up. The recommended approach is to create a single [`QueryClient`](https://tanstack.com/query/latest/docs/reference/QueryClient) in your preview file, clear it between stories via [`beforeEach`](https://storybook.js.org/docs/writing-tests/interaction-testing.md#beforeeach), and share the same instance through both `parameters.tanstack.router.context` and a `QueryClientProvider` decorator. ```tsx // .storybook/preview.tsx — CSF 3 // 👇 Create a new QueryClient const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, }, }, }); const preview: Preview = { beforeEach: () => { // 👇 Clear the cache between stories so each story starts fresh queryClient.clear(); }, parameters: { tanstack: { router: { // 👇 Make queryClient available in stories' beforeEach via ctx.context.queryClient context: { queryClient }, }, }, }, decorators: [ (Story) => ( // 👇 Provide the QueryClient to all stories ), ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // 👇 Create a new QueryClient const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, }, }, }); export default definePreview({ beforeEach: () => { // 👇 Clear the cache between stories so each story starts fresh queryClient.clear(); }, parameters: { tanstack: { router: { // 👇 Make queryClient available in stories' beforeEach via ctx.context.queryClient context: { queryClient }, }, }, }, decorators: [ (Story) => ( // 👇 Provide the QueryClient to all stories ), ], }); ``` #### Seeding query data per story In individual stories, use [`beforeEach`](https://storybook.js.org/docs/writing-tests/interaction-testing.md#beforeeach) to call [`setQueryData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetquerydata) on the shared `QueryClient` before the component renders. Access it from `parameters.tanstack.router.context`: ```ts // Navbar.stories.ts — CSF 3 const meta = { component: Navbar, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = {}; export const LoggedIn: Story = { beforeEach: async ({ parameters }) => { const qc: QueryClient = parameters.tanstack?.router?.context?.queryClient; qc?.setQueryData(['currentUser'], { id: 'user-1', name: 'Ada Lovelace', }); }, }; ``` ```ts // Navbar.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Navbar, }); export const Default = meta.story(); export const LoggedIn = meta.story({ beforeEach: async ({ parameters }) => { const qc: QueryClient = parameters.tanstack?.router?.context?.queryClient; qc?.setQueryData(['currentUser'], { id: 'user-1', name: 'Ada Lovelace', }); }, }); ``` ## FAQ ### How do I migrate from the `react-vite` framework? #### Automatic migration Storybook provides a migration tool for migrating to this framework from the React (Vite) framework, [`@storybook/react-vite`](https://storybook.js.org/docs/get-started/frameworks/react-vite.md). To migrate, run this command: ```bash npx storybook automigrate react-vite-to-tanstack-react ``` This automigration tool performs the following actions: 1. Updates `package.json` files to replace `@storybook/react-vite` with `@storybook/tanstack-react`. 2. Updates `.storybook/main.js|ts` to change the framework property (works with both regular and CSF factories `defineMain` configs). 3. Scans and updates import statements that reference `@storybook/react-vite` in your story files and Storybook configuration files (including `@storybook/react-vite/node` used by CSF factories). 4. Detects manual TanStack Router decorators in `.storybook/preview.*`, the rest of `.storybook/`, and any `*.stories.*` file. When one is found, the CLI offers to copy a ready-to-paste AI prompt to your clipboard that walks an AI assistant through removing the now-redundant decorator. `@storybook/tanstack-react` already wraps every story in a TanStack Router automatically, so any manual `RouterProvider` / `createRouter` / `createMemoryHistory` / `createRootRoute` decorator should be removed after running the automigration. For stories that need a specific route, use [`parameters.tanstack.router`](#rendering-a-route) instead. #### Manual migration First, install the framework: ```shell npm install --save-dev @storybook/tanstack-react ``` ```shell pnpm add --save-dev @storybook/tanstack-react ``` ```shell yarn add --dev @storybook/tanstack-react ``` Then, update your `.storybook/main.js|ts` to change the framework property: ```diff // .storybook/main.ts — CSF 3 - import type { StorybookConfig } from '@storybook/react-vite'; + import type { StorybookConfig } from '@storybook/tanstack-react'; const config: StorybookConfig = { // ... - framework: '@storybook/react-vite', + framework: '@storybook/tanstack-react', }; export default config; ``` ```diff // .storybook/main.ts — CSF Next 🧪 - import { defineMain } from '@storybook/react-vite/node'; + import { defineMain } from '@storybook/tanstack-react/node'; export default defineMain({ // ... - framework: '@storybook/react-vite', + framework: '@storybook/tanstack-react', }); ``` Then similarly update your `.storybook/preview.*` to import from `@storybook/tanstack-react`: ```diff // .storybook/preview.tsx — CSF 3 - import type { Preview } from '@storybook/react-vite'; + import type { Preview } from '@storybook/tanstack-react'; const preview: Preview = { //... }; export default preview; ``` ```diff // .storybook/preview.tsx — CSF Next 🧪 - import { definePreview } from '@storybook/react-vite'; + import { definePreview } from '@storybook/tanstack-react'; export default definePreview({ //... }); ``` `@storybook/tanstack-react` already wraps every story in a TanStack Router automatically, so any manual `RouterProvider` / `createRouter` / `createMemoryHistory` / `createRootRoute` decorator should be removed after running the automigration. For stories that need a specific route, use [`parameters.tanstack.router`](#rendering-a-route) instead. ### When should I use `@storybook/tanstack-react` instead of `@storybook/react-vite`? Use `@storybook/tanstack-react` when your components rely on TanStack Router or TanStack Start APIs and you want Storybook to provide router context, typed route parameters, automatic router mocking, and mocked TanStack Start server-function behavior. Use [`@storybook/react-vite`](https://storybook.js.org/docs/get-started/frameworks/react-vite.md) when your app is a standard React and Vite project without TanStack Router. ### My styles are missing in Storybook Import your application CSS in `.storybook/preview.*` so it is bundled with the preview: ```ts title=".storybook/preview.tsx" ``` For more information, see the [styling documentation](https://storybook.js.org/docs/configure/styling-and-css.md). ### How do I provide React context providers (e.g. theme, toast, auth) to all stories? Add [project-level decorators](https://storybook.js.org/docs/writing-stories/decorators.md#global-decorators) to apply providers to all stories. You can also add [component-level decorators](https://storybook.js.org/docs/writing-stories/decorators.md#component-decorators) to apply providers to all stories for a specific component, or [story-level decorators](https://storybook.js.org/docs/writing-stories/decorators.md#story-decorators) to apply providers to a single story. ### Does `@storybook/tanstack-react` support React Server Components? No. `@storybook/tanstack-react` runs stories in the browser using a memory-backed router. [React Server Components](https://react.dev/reference/rsc/server-components) require a server runtime and are not supported. If your component is a Server Component, [extract the client-side parts into a Client Component](https://react.dev/reference/rsc/server-components#adding-interactivity-to-server-components) and write stories for that instead. ### Story fails to render with an error about modules not providing a default export This usually means a server-only module is being imported in the browser. Check the error stack trace to find the module and add a Storybook mock for it as described in [Handling server-only dependencies](#handling-server-only-dependencies). ## API ### Modules The package exports these additional modules: #### `@storybook/tanstack-react/react-router` [TanStack Router](https://tanstack.com/router/latest/docs)-compatible mock implementations used by the framework to provide router behavior in stories. Import from this module when you need direct access to the mock APIs (for example, to assert against navigation spies in tests). #### `@storybook/tanstack-react/start` [TanStack Start](https://tanstack.com/start/latest/docs)-compatible mock implementations, including a mocked [`createServerFn()`](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions) implementation. Import from this module when a story or test needs to interact directly with the Start mock layer. ### Options You can pass an options object for additional configuration if needed: ```ts // .storybook/main.ts — CSF 3 const config: StorybookConfig = { framework: { name: '@storybook/tanstack-react', options: { builder: { // Vite builder options }, }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 export default defineMain({ framework: { name: '@storybook/tanstack-react', options: { builder: { // Vite builder options }, }, }, }); ``` The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). Available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). ### Parameters This framework contributes the following [parameters](https://storybook.js.org/docs/writing-stories/parameters.md) to Storybook under the `tanstack.router` namespace: When `route` is supplied as a plain object, it may also include TanStack route options such as [`head`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#head-method), [`search`](https://tanstack.com/router/latest/docs/guide/search-params#reading-search-params), and [`params.parse`](https://tanstack.com/router/latest/docs/api/router/RouteOptionsType#paramsparse-method). #### `context` Type: `Record` Router context values injected into the story router. #### `params` Type: `ResolveParams` Interpolates route params into the current path. When `route` is a typed file route, the type is constrained to the param names declared in that route's path (for example, `{ id: string }` for `/$id`). #### `path` Type: `string` Sets the initial URL path for the story router. #### `query` Type: `Record` Appends search params to the initial URL. #### `route` Type: `AnyRoute | route options object` Supplies a route instance directly or creates a temporary story route from route options. Storybook extracts the route's React component automatically from the route. #### `routeOverrides` Type: `Partial>` Per-route overrides keyed by route ID, applied to the story's route and root route. Use `'__root__'` to target the root route. Each entry can override `loader`, `beforeLoad`, `validateSearch`, `loaderDeps`, and `context`. #### `useRouterContext` Type: `({ storyContext }) => RouterContext` Dynamically computes the router context from the story context. Use this when the router context depends on values that are already available in the story (for example, a `QueryClient` that is loaded by a story loader). ```ts parameters: { tanstack: { router: { useRouterContext: ({ storyContext }) => ({ queryClient: storyContext.loaded.queryClient, }), }, }, } ``` This is an alternative to [`context`](#context) for cases where the router context needs a React context provider (e.g., TanStack Query's `QueryClientProvider`) that must be rendered in the story before the context value can be accessed. > Source: https://storybook.js.org/docs/get-started/frameworks/tanstack-react --- # Storybook for Vue with Vite Storybook for Vue & Vite is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for [Vue](https://vuejs.org/) applications built with [Vite](https://vitejs.dev/). ## Install To install Storybook in an existing Vue project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## Configure Storybook for Vue 3 with Vite is designed to work out of the box with minimal configuration. This section covers configuration options for the framework. ### Extending the Vue application Storybook creates a [Vue 3 application](https://vuejs.org/api/application.html#application-api) for your component preview. When using global custom components (`app.component`), directives (`app.directive`), extensions (`app.use`), or other application methods, you will need to configure those in the `./storybook/preview.ts` file. Therefore, Storybook provides you with a `setup` function exported from this package. This function receives your Storybook instance as a callback, which you can interact with and add your custom configuration. ```js title=".storybook/preview.ts" setup((app) => { app.use(MyPlugin); app.component('my-component', MyComponent); app.mixin({ // My mixin }); }); ``` ### Using `vue-component-meta` `vue-component-meta` is only available in Storybook ≥ 8. It is currently an opt-in, but it will become the default in a future version of Storybook. [`vue-component-meta`](https://github.com/vuejs/language-tools/tree/master/packages/component-meta) is a tool maintained by the Vue team that extracts metadata from Vue components. Storybook can use it to generate the [controls](https://storybook.js.org/docs/essentials/controls.md) for your stories and documentation. It's a more full-featured alternative to `vue-docgen-api` and is recommended for most projects. If you want to use `vue-component-meta`, you can configure it in your `.storybook/main.js|ts` file: ```ts title=".storybook/main.ts" const config: StorybookConfig = { framework: { name: '@storybook/vue3-vite', options: { docgen: 'vue-component-meta', }, }, }; export default config; ``` `vue-component-meta` comes with many benefits and enables more documentation features, such as: #### Support for multiple component types `vue-component-meta` supports all types of Vue components (including SFC, functional, composition/options API components) from `.vue`, `.ts`, `.tsx`, `.js`, and `.jsx` files. It also supports both default and named component exports. #### Prop description and JSDoc tag annotations To describe a prop, including tags, you can use JSDoc comments in your component's props definition: ```html title="YourComponent.vue" ``` The props definition above will generate the following controls: ![Controls generated from props](../../_assets/get-started/vue-component-meta-prop-types-controls.png) #### Events types extraction To provide a type for an emitted event, you can use TypeScript types (including JSDoc comments) in your component's `defineEmits` call: ```html title="YourComponent.vue" ``` Which will generate the following controls: ![Controls generated from events](../../_assets/get-started/vue-component-meta-event-types-controls.png) #### Slots types extraction The slot types are automatically extracted from your component definition and displayed in the controls panel. ```html title="YourComponent.vue" ``` If you use `defineSlots`, you can describe each slot using JSDoc comments in your component's slots definition: ```ts defineSlots<{ /** Example description for default */ default(props: { num: number }): any; /** Example description for named */ named(props: { str: string }): any; /** Example description for no-bind */ noBind(props: {}): any; /** Example description for vbind */ vbind(props: { num: number; str: string }): any; }>(); ``` The definition above will generate the following controls: ![Controls generated from slots](../../_assets/get-started/vue-component-meta-slot-types-controls.png) #### Exposed properties and methods The properties and methods exposed by your component are automatically extracted and displayed in the [Controls](https://storybook.js.org/docs/essentials/controls.md) panel. ```html title="YourComponent.vue" ``` The definition above will generate the following controls: ![Controls generated from exposed properties and methods](../../_assets/get-started/vue-component-meta-exposed-types-controls.png) #### Override the default configuration If you're working with a project that relies on [`tsconfig references`](https://www.typescriptlang.org/docs/handbook/project-references.html) to link to other existing configuration files (e.g., `tsconfig.app.json`, `tsconfig.node.json`), we recommend that you update your [`.storybook/main.js|ts`](https://storybook.js.org/docs/configure.md) configuration file and add the following: ```ts title=".storybook/main.ts" const config: StorybookConfig = { framework: { name: '@storybook/vue3-vite', options: { docgen: { plugin: 'vue-component-meta', tsconfig: 'tsconfig.app.json', }, }, }, }; export default config; ``` This is not a limitation of Storybook, but how `vue-component-meta` works. For more information, refer to the appropriate [GitHub issue](https://github.com/vuejs/language-tools/issues/3896). Otherwise, you might face missing component types/descriptions or unresolvable import aliases like `@/some/import`. ## FAQ ### How do I manually install the Vue framework? First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: ### Storybook doesn't work with my Vue 2 project [Vue 2 entered End of Life](https://v2.vuejs.org/lts/) (EOL) on December 31st, 2023, and is no longer maintained by the Vue team. As a result, Storybook no longer supports Vue 2. We recommend you upgrade your project to Vue 3, which Storybook fully supports. If that's not an option, you can still use Storybook with Vue 2 by installing the latest version of Storybook 7 with the following command: ```shell npx storybook@^7 init ``` ```shell pnpm dlx storybook@^7 init ``` ```shell yarn dlx storybook@^7 init ``` ## API ### Options You can pass an options object for additional configuration if needed: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). #### `docgen` Type: `'vue-docgen-api' | 'vue-component-meta' | boolean` Default: `'vue-docgen-api'` Since: `8.0` Choose which docgen tool to use when generating controls for your components. See [Using `vue-component-meta`](#using-vue-component-meta) for more information. Set to `false` to disable docgen processing entirely for improved build performance. ```ts title=".storybook/main.ts" const config: StorybookConfig = { framework: { name: '@storybook/vue3-vite', options: { docgen: false, // Disable docgen for better performance }, }, }; export default config; ``` ##### When to disable docgen Disabling docgen can improve build performance for large projects, but [argTypes won't be inferred automatically](https://storybook.js.org/docs/api/arg-types.md#automatic-argtype-inference), which will prevent features like [Controls](https://storybook.js.org/docs/essentials/controls.md) and [docs](https://storybook.js.org/docs/writing-docs/autodocs.md) from working as expected. To use those features, you will need to [define `argTypes` manually](https://storybook.js.org/docs/api/arg-types.md#manually-specifying-argtypes). > Source: https://storybook.js.org/docs/get-started/frameworks/vue3-vite --- # Storybook for Web components with Vite Storybook for Web components & Vite is a [framework](https://storybook.js.org/docs/contribute/framework.md) that makes it easy to develop and test UI components in isolation for applications using [Web components](https://www.webcomponents.org/introduction) built with [Vite](https://vitejs.dev/). ## Install To install Storybook in an existing project, run this command in your project's root directory: ```shell npm create storybook@latest ``` ```shell pnpm create storybook@latest ``` ```shell yarn create storybook ``` You can then get started [writing stories](https://storybook.js.org/docs/get-started/whats-a-story.md), [running tests](https://storybook.js.org/docs/writing-tests.md) and [documenting your components](https://storybook.js.org/docs/writing-docs.md). For more control over the installation process, refer to the [installation guide](https://storybook.js.org/docs/get-started/install.md). ### Requirements ## Run Storybook To run Storybook for a particular project, run the following: ```shell npm run storybook ``` ```shell pnpm run storybook ``` ```shell yarn storybook ``` To build Storybook, run: ```shell npm run build-storybook ``` ```shell pnpm run build-storybook ``` ```shell yarn build-storybook ``` You will find the output in the configured `outputDir` (default is `storybook-static`). ## FAQ ### How do I manually install the Web Components framework? First, install the framework: Then, update your `.storybook/main.js|ts` to change the framework property: ## API ### Options You can pass an options object for additional configuration if needed: The available options are: #### `builder` Type: `Record` Configure options for the [framework's builder](https://storybook.js.org/docs/api/main-config/main-config-framework.md#optionsbuilder). For this framework, available options can be found in the [Vite builder docs](https://storybook.js.org/docs/builders/vite.md). > Source: https://storybook.js.org/docs/get-started/frameworks/web-components-vite --- # What's a story? A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the “interesting” states a component can support. When you installed Storybook, the CLI created example components that demonstrate the types of components you can build with Storybook: Button, Header, and Page. Each example component has a set of stories that show the states it supports. You can browse the stories in the UI and see the code behind them in files that end with `.stories.js|ts`. The stories are written in [Component Story Format](https://storybook.js.org/docs/api/csf.md) (CSF), an ES6 modules-based standard for writing component examples. Let’s start with the `Button` component. A story is an object that describes how to render the component in question. Here’s how to render `Button` in the “primary” state and export a story called `Primary`. ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { primary: true, label: 'Button', }, }); ``` ![Button story with args](../_assets/get-started/example-button-args.png) View the rendered `Button` by clicking on it in the Storybook sidebar. Note how the values specified in [`args`](https://storybook.js.org/docs/writing-stories/args.md) are used to render the component and match those represented in the [Controls](https://storybook.js.org/docs/essentials/controls.md) panel. Using `args` in your stories has additional benefits: - `Button`'s callbacks are logged into the [Actions](https://storybook.js.org/docs/essentials/actions.md) panel. Click to try it. - `Button`'s arguments are dynamically editable in the Controls panel. Adjust the controls. ## Working with stories Storybook makes it easy to work on one component in one state (aka a story) at a time. When you edit a component's code or its stories, Storybook will instantly re-render in the browser. No need to refresh manually. ### Create a new story If you're working on a component that does not yet have any stories, you can click the ➕ button in the sidebar to search for your component and have a basic story created for you. You can also create a story file for your new story. We recommend copy/pasting an existing story file next to the component source file, then adjusting it for your component. If you're working on a component that already has other stories, you can use the [Controls panel](https://storybook.js.org/docs/essentials/controls.md) to adjust the value of a control and then save those changes as a new story. Or, if you prefer, edit the story file's code to add a new named export for your story: ### Edit a story Using the [Controls panel](https://storybook.js.org/docs/essentials/controls.md), update a control's value for a story. You can then save the changes to the story and the story file's code will be updated for you. Of course, you can always update the story's code directly too: Stories are also helpful for checking that the UI continues to look correct as you make changes. The `Button` component has four stories that show it in different use cases. View those stories now to confirm that your change to `Primary` didn’t introduce unintentional bugs in the other stories. Checking component’s stories as you develop helps prevent accidental regressions. [Tools that integrate with Storybook can automate this](https://storybook.js.org/docs/writing-tests.md) for you. Now that we’ve seen the basic anatomy of a story let’s see how we use Storybook’s UI to develop stories. > Source: https://storybook.js.org/docs/get-started/whats-a-story --- # Browse Stories Last chapter, we learned that stories correspond with discrete component states. This chapter demonstrates how to use Storybook as a workshop for building components. ## Sidebar and Preview A `*.stories.js|ts|svelte` file defines all the stories for a component. Each story has a corresponding sidebar item. When you click on a story, it renders in an isolated preview iframe. The sidebar contains three areas: sidebar search, story explorer, and [testing widget](https://storybook.js.org/docs/writing-tests.md). Try the sidebar search to find a story by name. Navigate between stories by clicking on them in the explorer. Run component tests for all stories using the widget at the bottom of the sidebar. Or use keyboard shortcuts. Click on the Storybook menu to see a list of available shortcuts. ![Storybook keyboard shortcuts examples](../_assets/get-started/storybook-keyboard-shortcuts.png) Storybook supports fast keyboard navigation between landmark regions. Press F6 and Shift+F6 to navigate between the sidebar, toolbar, preview, and addons panel. ## Toolbar Storybook ships with time-saving tools built-in. The toolbar contains tools that allow you to adjust how the story renders in the Canvas: - 🔍 Zooming visually scales the component so you can check the details. - 🖼 Background changes the rendered background behind your component so you can verify how your component renders in different visual contexts. - 📐 Grid renders your component on top of a grid layout so you can verify if your component is aligned correctly. - 📏 Measure toggles a measurement overlay to help you inspect the dimensions of components. - 🎚️ Outline displays the component's bounding box so you can verify if your component is positioned correctly. - 📱 Viewport renders the component in a variety of dimensions and orientations. It’s ideal for checking the responsiveness of components. The [“Docs”](https://storybook.js.org/docs/writing-docs.md) page displays auto-generated documentation for components (inferred from the source code). Usage documentation is helpful when sharing reusable components with your team, for example, in an application. ![Storybook keyboard shortcuts examples](../_assets/get-started/mdx-example.png) The toolbar is customizable. You can use [globals](https://storybook.js.org/docs/essentials/toolbars-and-globals.md) to quickly toggle themes and languages. Or install Storybook toolbar [addons](https://storybook.js.org/docs/configure/user-interface/storybook-addons.md) from the community to enable advanced workflows. ## Addons Addons are plugins that extend Storybook's core functionality. You can find them in the addons panel, a reserved place in the Storybook UI below the Canvas. Each tab shows the generated metadata, logs, or static analysis for the selected story by the addon. ![Storybook addon examples](../_assets/get-started/addons.png) - **Controls** allows you to interact with a component’s args (inputs) dynamically. Experiment with alternate configurations of the component to discover edge cases. - **Actions** help you verify interactions produce the correct outputs via callbacks. For instance, if you view the “Logged In” story of the `Header` component, we can verify that clicking the “Log out” button triggers the `onLogout` callback, which would be provided by the component that made use of the Header. - **Interactions** provides a helpful user interface for debugging [interaction tests](https://storybook.js.org/docs/writing-tests/interaction-testing.md) with the `play` function. - **Accessibility** helps you identify [accessibility violations](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) in your components. - **Visual Tests** lets you pinpoint UI bugs in your local development environment by providing instant feedback directly in Storybook. Storybook is extensible. Our rich ecosystem of addons helps you test, document, and optimize your stories. You can also create an addon to satisfy your workflow requirements. Read more in the [addons section](https://storybook.js.org/docs/addons.md). In the next chapter, we'll get your components rendering in Storybook so you can use it to supercharge component development. ## Use stories to build UIs When building apps, one of the biggest challenges is to figure out if a piece of UI already exists in your codebase and how to use it for the new feature you're building. Storybook catalogues all your components and their use cases. Therefore, you can quickly browse it to find what you're looking for. Here's what the workflow looks like: - 🗃 Use the sidebar to find a suitable component - 👀 Review its stories to pick a variant that suits your needs - 📝 Copy/paste the story definition into your app code and wire it up to data You can access the story definition from the stories file or make it available in your published Storybook using the [Docs addon](https://storybook.js.org/docs/api/doc-blocks/doc-block-source.md). ![Docblock source](../_assets/get-started/docblock-source.png) > Source: https://storybook.js.org/docs/get-started/browse-stories --- # Setup Storybook Now that you’ve learned what stories are and how to browse them, let’s demo working on one of your components. Pick a simple component from your project, like a Button, and write a `.stories.js`, `.stories.ts`, or `.stories.svelte` file to go along with it. It might look something like this: ```ts // YourComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 This default export determines where your story goes in the story list const meta = { component: YourComponent, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { args: { //👇 The args you need here will depend on your component }, }; ``` ```ts // YourComponent.stories.ts|tsx — CSF Next 🧪 //👇 This default export determines where your story goes in the story list const meta = preview.meta({ component: YourComponent, }); export const Basic = meta.story({ args: { //👇 The args you need here will depend on your component }, }); ``` Go to your Storybook to view the rendered component. It’s OK if it looks a bit unusual right now. Depending on your technology stack, you also might need to configure the Storybook environment further. ## Render component styles Storybook isn’t opinionated about how you generate or load CSS. It renders whatever DOM elements you provide. But sometimes, things won’t “look right” out of the box. You may have to configure your CSS tooling for Storybook’s rendering environment. Here are some setup guides for popular tools in the community. - [Tailwind](https://storybook.js.org/recipes/tailwindcss/) - [Material UI](https://storybook.js.org/recipes/@mui/material/) - [Vuetify](https://storybook.js.org/recipes/vuetify/) - [Styled Components](https://storybook.js.org/recipes/styled-components/) - [Emotion](https://storybook.js.org/recipes/@emotion/styled/) - [Sass](https://storybook.js.org/recipes/sass/) - [Bootstrap](https://storybook.js.org/recipes/bootstrap/) - [Less](https://storybook.js.org/recipes/less/) - [Vanilla-extract](https://storybook.js.org/recipes/@vanilla-extract/css/) Don't see the tool that you're looking for? Check out the [styling and css](https://storybook.js.org/docs/configure/styling-and-css.md) page for more details. ## Configure Storybook for your stack Storybook comes with a permissive [default configuration](https://storybook.js.org/docs/configure.md). It attempts to customize itself to fit your setup. But it’s not foolproof. Your project may have additional requirements before components can be rendered in isolation. This warrants customizing configuration further. There are three broad categories of configuration you might need.
Build configuration like Webpack and Babel If you see errors on the CLI when you run the `yarn storybook` command, you likely need to make changes to Storybook’s build configuration. Here are some things to try: - [Presets](https://storybook.js.org/docs/addons/addon-types.md) bundle common configurations for various technologies into Storybook. In particular, presets exist for Create React App and Ant Design. - Specify a custom [Babel configuration](https://storybook.js.org/docs/configure/integration/compilers.md#babel) for Storybook. Storybook automatically tries to use your project’s config if it can. - Adjust the [Webpack configuration](https://storybook.js.org/docs/builders/webpack.md) that Storybook uses. Try patching in your own configuration if needed.
Runtime configuration If Storybook builds but you see an error immediately when connecting to it in the browser, in that case, chances are one of your input files is not compiling/transpiling correctly to be interpreted by the browser. Storybook supports evergreen browsers, but you may need to check the Babel and Webpack settings (see above) to ensure your component code works correctly.
Component context If a particular story has a problem rendering, often it means your component expects a specific environment is available to the component. A common frontend pattern is for components to assume that they render in a specific “context” with parent components higher up the rendering hierarchy (for instance, theme providers). Use [decorators](https://storybook.js.org/docs/writing-stories/decorators.md) to “wrap” every story in the necessary context providers. The [`.storybook/preview.*`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file allows you to customize how components render in Canvas, the preview iframe. This file can be written in JavaScript (`preview.jsx`) or TypeScript (`preview.tsx`). See how you can wrap every component rendered in Storybook with [Styled Components](https://styled-components.com/) `ThemeProvider`, [Vue's Vuetify](https://vuetifyjs.com/en/), Svelte's [Bits UI](https://bits-ui.com/) `BitsConfig`, or with an Angular theme provider component in the example below. Use [decorators](https://storybook.js.org/docs/writing-stories/decorators.md) to “wrap” every story in the necessary context providers. The [`.storybook/preview.*`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file allows you to customize how components render in Canvas, the preview iframe. This file can be written in JavaScript (`preview.js`) or TypeScript (`preview.ts`). See how you can wrap every component rendered in Storybook with [Styled Components](https://styled-components.com/) `ThemeProvider`, [Vue's Vuetify](https://vuetifyjs.com/en/), Svelte's [Bits UI](https://bits-ui.com/) `BitsConfig`, or with an Angular theme provider component in the example below. ```tsx // .storybook/preview.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const preview: Preview = { decorators: [ (Story) => ( ), ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ decorators: [ (Story) => ( ), ], }); ```
## Load assets and resources We recommend serving external resources and assets requested in your components statically with Storybook. It ensures that assets are always available to your stories. Read our [documentation](https://storybook.js.org/docs/configure/integration/images-and-assets.md) to learn how to host static files with Storybook. > Source: https://storybook.js.org/docs/get-started/setup --- # Conclusion Congratulations! You learned the basics. Storybook is the most popular tool for UI component development and documentation. You’ll be able to transfer these skills to thousands of companies that use Storybook to build UIs including GitHub, Airbnb, and Stripe. If you’d like to learn workflows for building app UIs with Storybook, check out our in-depth guides over at the [tutorials](https://storybook.js.org/tutorials/) page. Continue reading for detailed information on how to use Storybook APIs. - [How to write stories](https://storybook.js.org/docs/writing-stories.md) - [How to document components and design systems](https://storybook.js.org/docs/writing-docs.md) - [View example Storybooks from leading companies](https://storybook.js.org/showcase) > Source: https://storybook.js.org/docs/get-started/conclusion --- # How to write stories A story captures the rendered state of a UI component. It's an object with annotations that describe the component's behavior and appearance given a set of arguments. Storybook uses the generic term arguments (args for short) when talking about React’s `props`, Vue’s `props`, Angular’s `@Input`, and other similar concepts. ## Where to put stories A component’s stories are defined in a story file that lives alongside the component file. The story file is for development-only, and it won't be included in your production bundle. In your filesystem, it looks something like this: ``` components/ ├─ Button/ │ ├─ Button.js | ts | jsx | tsx | vue | svelte │ ├─ Button.stories.js | ts | jsx | tsx | svelte ``` ## Component Story Format We define stories according to the [Component Story Format](https://storybook.js.org/docs/api/csf.md) (CSF), an ES6 module-based standard that is easy to write and portable between tools. The key ingredients are the meta (or [default export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Using_the_default_export)) that describes the component, and [named exports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Using_named_exports) that describe the stories. ### Default export The _default_ export metadata controls how Storybook lists your stories and provides information used by addons. For example, here’s the meta (or default export) for a story file `Button.stories.js|ts`: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); ``` Starting with Storybook version 7.0, story titles are analyzed statically as part of the build process. The _default_ export must contain a `title` property that can be read statically or a `component` property from which an automatic title can be computed. Using the `id` property to customize your story URL must also be statically readable. ### Defining stories Use the _named_ exports of a CSF file to define your component’s stories. We recommend you use UpperCamelCase for your story exports. Here’s how to render `Button` in the “primary” state and export a story called `Primary`. ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { primary: true, label: 'Button', }, }); ``` #### Custom rendering By default, stories will render the component defined in the meta (or default export), with the `args` passed to it. If you need to render something else, you can provide a function to the `render` property that returns the desired output. For example, if you want to render a `Button` inside an `Alert`, you can define a custom render function like this: ```tsx // Button.stories.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const PrimaryInAlert: Story = { args: { primary: true, label: 'Button', }, render: (args) => ( Alert text ), }; ``` ```tsx // Button.stories.tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const PrimaryInAlert = meta.story({ args: { primary: true, label: 'Button', }, render: (args) => ( Alert text ), }); ``` Note how the `render` function spreads `args` onto the Button component. This ensures that features like [Controls](https://storybook.js.org/docs/essentials/controls.md) will work as expected, allowing you to dynamically change the Button's properties in the Storybook UI. You can re-use the same render function across stories by applying it at the meta level: ```tsx // Button.stories.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, render: (args) => ( Alert text ), } satisfies Meta; export default meta; type Story = StoryObj; export const DefaultInAlert: Story = { args: { label: 'Button', }, }; export const PrimaryInAlert: Story = { args: { primary: true, label: 'Button', }, }; ``` ```tsx // Button.stories.tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, render: (args) => ( Alert text ), }); export const DefaultInAlert = meta.story({ args: { label: 'Button', }, }); export const PrimaryInAlert = meta.story({ args: { primary: true, label: 'Button', }, }); ``` Whatever you define at the meta level can be overridden at the story level, so you can still customize the rendering of individual stories if needed. Finally, `render` functions receive a second `context` argument, which contains all other details for the story, including [`parameters`](https://storybook.js.org/docs/writing-stories/parameters.md), [`globals`](https://storybook.js.org/docs/essentials/toolbars-and-globals.md), and more. #### Working with React Hooks [React Hooks](https://react.dev/reference/react) are convenient helper methods to create components using a more streamlined approach. You can use them while creating your component's stories if you need them, although you should treat them as an advanced use case. We **recommend** [args](https://storybook.js.org/docs/writing-stories/args.md) as much as possible when writing your own stories. As an example, here’s a story that uses React Hooks to change the button's state: ```tsx // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; const ButtonWithHooks = () => { // Sets the hooks for both the label and primary props const [value, setValue] = useState('Secondary'); const [isPrimary, setIsPrimary] = useState(false); // Sets a click handler to change the label's value const handleOnChange = () => { if (!isPrimary) { setIsPrimary(true); setValue('Primary'); } }; return ; }; export const Primary = { render: () => , } satisfies Story; ``` ```tsx // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); const ButtonWithHooks = () => { // Sets the hooks for both the label and primary props const [value, setValue] = useState('Secondary'); const [isPrimary, setIsPrimary] = useState(false); // Sets a click handler to change the label's value const handleOnChange = () => { if (!isPrimary) { setIsPrimary(true); setValue('Primary'); } }; return ; }; export const Primary = meta.story({ render: () => , }); ``` ### Rename stories By default, Storybook uses the name of the story export as the basis for the story name. However, you can customize the name of your story by adding a `name` property to the story object. This is useful when you want to provide a more descriptive or user-friendly name for your story. ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { // 👇 Rename this story name: 'I am the primary', args: { label: 'Button', primary: true, }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ // 👇 Rename this story name: 'I am the primary', args: { label: 'Button', primary: true, }, }); ``` Your story will now be shown in the sidebar with the given text.
## How to write stories A story is an object that describes how to render a component. You can have multiple stories per component, and those stories can build upon one another. For example, we can add Secondary and Tertiary stories based on our Primary story from above. ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { backgroundColor: '#ff0', label: 'Button', }, }; export const Secondary: Story = { args: { ...Primary.args, label: '😄👍😍💯', }, }; export const Tertiary: Story = { args: { ...Primary.args, label: '📚📕📈🤓', }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { backgroundColor: '#ff0', label: 'Button', }, }); export const Secondary = meta.story({ args: { ...Primary.input.args, label: '😄👍😍💯', }, }); export const Tertiary = meta.story({ args: { ...Primary.input.args, label: '📚📕📈🤓', }, }); ``` What’s more, you can import `args` to reuse when writing stories for other components, and it's helpful when you’re building composite components. For example, if we make a `ButtonGroup` story, we might remix two stories from its child component `Button`. ```ts // ButtonGroup.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 Imports the Button stories const meta = { component: ButtonGroup, } satisfies Meta; export default meta; type Story = StoryObj; export const Pair: Story = { args: { buttons: [{ ...ButtonStories.Primary.args }, { ...ButtonStories.Secondary.args }], orientation: 'horizontal', }, }; ``` ```ts // ButtonGroup.stories.ts|tsx — CSF Next 🧪 //👇 Imports the Button stories const meta = preview.meta({ component: ButtonGroup, }); export const Pair = meta.story({ args: { buttons: [{ ...ButtonStories.Primary.input.args }, { ...ButtonStories.Secondary.input.args }], orientation: 'horizontal', }, }); ``` When Button’s signature changes, you only need to change Button’s stories to reflect the new schema, and ButtonGroup’s stories will automatically be updated. This pattern allows you to reuse your data definitions across the component hierarchy, making your stories more maintainable. That’s not all! Each of the args from the story function are live editable using Storybook’s [Controls](https://storybook.js.org/docs/essentials/controls.md) panel. It means your team can dynamically change components in Storybook to stress test and find edge cases. You can also use the Controls panel to edit or save a new story after adjusting its control values. Addons can enhance args. For instance, [Actions](https://storybook.js.org/docs/essentials/actions.md) auto-detects which args are callbacks and appends a logging function to them. That way, interactions (like clicks) get logged in the actions panel. ### Using the play function Storybook's `play` function is a convenient helper methods to test component scenarios that otherwise require user intervention. They're small code snippets that execute once your story renders. For example, suppose you wanted to validate a form component, you could write the following story using the `play` function to check how the component responds when filling in the inputs with information: ```ts // LoginForm.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: LoginForm, } satisfies Meta; export default meta; type Story = StoryObj; export const EmptyForm: Story = {}; export const FilledForm: Story = { play: async ({ canvas, userEvent }) => { // 👇 Simulate interactions with the component await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); // See https://storybook.js.org/docs/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel await userEvent.click(canvas.getByRole('button')); // 👇 Assert DOM structure await expect( canvas.getByText( 'Everything is perfect. Your account is ready and we should probably get you started!', ), ).toBeInTheDocument(); }, }; ``` ```ts // LoginForm.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: LoginForm, }); export const EmptyForm = meta.story(); export const FilledForm = meta.story({ play: async ({ canvas, userEvent }) => { // 👇 Simulate interactions with the component await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); // See https://storybook.js.org/docs/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel await userEvent.click(canvas.getByRole('button')); // 👇 Assert DOM structure await expect( canvas.getByText( 'Everything is perfect. Your account is ready and we should probably get you started!', ), ).toBeInTheDocument(); }, }); ``` You can interact with and debug your story's play function in the [interactions panel](https://storybook.js.org/docs/writing-tests/interaction-testing.md#debugging-interaction-tests). ### Using parameters Parameters are Storybook’s method of defining static metadata for stories. A story’s parameters can be used to provide configuration to various addons at the level of a story or group of stories. For instance, suppose you wanted to test your Button component against a different set of backgrounds than the other components in your app. You might add a component-level `backgrounds` parameter: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, //👇 Creates specific parameters at the component level parameters: { backgrounds: { options: {}, }, }, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, //👇 Creates specific parameters at the component level parameters: { backgrounds: { options: {}, }, }, }); ``` ![Parameters background color](../_assets/writing-stories/parameters-background-colors.png) This parameter would instruct the backgrounds feature to reconfigure itself whenever a Button story is selected. Most features and addons are configured via a parameter-based API and can be influenced at a [global](https://storybook.js.org/docs/writing-stories/parameters.md#global-parameters), [component](https://storybook.js.org/docs/writing-stories/parameters.md#component-parameters), and [story](https://storybook.js.org/docs/writing-stories/parameters.md#story-parameters) level. ### Using decorators Decorators are a mechanism to wrap a component in arbitrary markup when rendering a story. Components are often created with assumptions about ‘where’ they render. Your styles might expect a theme or layout wrapper, or your UI might expect specific context or data providers. A simple example is adding padding to a component’s stories. Accomplish this using a decorator that wraps the stories in a `div` with padding, like so: ```tsx // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, decorators: [ (Story) => (
), ], } satisfies Meta; export default meta; ``` ```tsx // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, decorators: [ (Story) => (
), ], }); ``` Decorators [can be more complex](https://storybook.js.org/docs/writing-stories/decorators.md#context-for-mocking) and are often provided by [addons](https://storybook.js.org/docs/configure/user-interface/storybook-addons.md). You can also configure decorators at the [story](https://storybook.js.org/docs/writing-stories/decorators.md#story-decorators), [component](https://storybook.js.org/docs/writing-stories/decorators.md#component-decorators) and [global](https://storybook.js.org/docs/writing-stories/decorators.md#global-decorators) level. ## Stories for two or more components Sometimes you may have two or more components created to work together. For instance, if you have a parent `List` component, it may require child `ListItem` components. ```ts // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; // Always an empty list, not super interesting export const Empty: Story = {}; ``` ```ts // List.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: List, }); // Always an empty list, not super interesting export const Empty = meta.story(); ``` In such cases, it makes sense to [customize the rendering](#custom-rendering) to output the `List` component with different numbers of `ListItem` children. ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; export const Empty: Story = {}; /* *👇 Render functions are a framework specific feature to allow you control on how the component renders. * See https://storybook.js.org/docs/api/csf * to learn how to use render functions. */ export const OneItem: Story = { render: (args) => ( ), }; export const ManyItems: Story = { render: (args) => ( ), }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: List, }); export const Empty = meta.story(); /* *👇 Render functions are a framework specific feature to allow you control on how the component renders. * See https://storybook.js.org/docs/api/csf * to learn how to use render functions. */ export const OneItem = meta.story({ render: (args) => ( ), }); export const ManyItems = meta.story({ render: (args) => ( ), }); ``` You can also reuse _story data_ from the child `ListItem` in your `List` component. That’s easier to maintain because you don’t have to update it in multiple places. ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 We're importing the necessary stories from ListItem const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; export const ManyItems: Story = { render: (args) => ( ), }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 //👇 We're importing the necessary stories from ListItem const meta = preview.meta({ component: List, }); export const ManyItems = meta.story({ render: (args) => ( ), }); ``` Note that there are disadvantages in writing stories like this as you cannot take full advantage of the args mechanism and composing args as you build even more complex composite components. For more discussion, see the [multi component stories](https://storybook.js.org/docs/writing-stories/stories-for-multiple-components.md) workflow documentation. > Source: https://storybook.js.org/docs/writing-stories --- # Args A story is a component with a set of arguments that define how the component should render. “Args” are Storybook’s mechanism for defining those arguments in a single JavaScript object. Args can be used to dynamically change props, slots, styles, inputs, etc. It allows Storybook and its addons to live edit components. You _do not_ need to modify your underlying component code to use args. When an arg’s value changes, the component re-renders, allowing you to interact with components in Storybook’s UI via addons that affect args. Learn how and why to write stories in [the introduction](https://storybook.js.org/docs/writing-stories.md). For details on how args work, read on. ## Args object The `args` object can be defined at the [story](#story-args), [component](#component-args) and [global level](#global-args). It is a JSON serializable object composed of string keys with matching valid value types that can be passed into a component for your framework. ## Story args To define the args of a single story, use the `args` CSF story key: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { primary: true, label: 'Button', }, }); ``` These args will only apply to the story for which they are attached, although you can [reuse](https://storybook.js.org/docs/writing-stories/build-pages-with-storybook.md#args-composition-for-presentational-screens) them via JavaScript object reuse: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the name of your framework const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; export const PrimaryLongName: Story = { args: { ...Primary.args, label: 'Primary with a really long name', }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { primary: true, label: 'Button', }, }); export const PrimaryLongName = meta.story({ args: { ...Primary.input.args, label: 'Primary with a really long name', }, }); ``` In the above example, we use the [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) feature of ES 2015. ## Component args You can also define args at the component level; they will apply to all the component's stories unless you overwrite them. To do so, use the `args` key on the `default` CSF export: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, //👇 Creates specific argTypes argTypes: { backgroundColor: { control: 'color' }, }, args: { //👇 Now all Button stories will be primary. primary: true, }, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, //👇 Creates specific argTypes argTypes: { backgroundColor: { control: 'color' }, }, args: { //👇 Now all Button stories will be primary. primary: true, }, }); ``` ## Global args You can also define args at the global level; they will apply to every component's stories unless you overwrite them. To do so, define the `args` property in the default export of `preview.*`: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { // The default value of the theme arg for all stories args: { theme: 'light' }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ // The default value of the theme arg for all stories args: { theme: 'light' }, }); ``` For most uses of global args, [globals](https://storybook.js.org/docs/essentials/toolbars-and-globals.md) are a better tool for defining globally-applied settings, such as a theme. Using globals enables users to change the value with the toolbar menu. ## Args composition You can separate the arguments to a story to compose in other stories. Here's how you can combine args for multiple stories of the same component. ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the name of your framework const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, label: 'Button', }, }; export const Secondary: Story = { args: { ...Primary.args, primary: false, }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ args: { primary: true, label: 'Button', }, }); export const Secondary = meta.story({ args: { ...Primary.input.args, primary: false, }, }); ``` If you find yourself re-using the same args for most of a component's stories, you should consider using [component-level args](#component-args). Args are useful when writing stories for composite components that are assembled from other components. Composite components often pass their arguments unchanged to their child components, and similarly, their stories can be compositions of their child components stories. With args, you can directly compose the arguments: ```ts // Page.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 Imports all Header stories const meta = { component: Page, } satisfies Meta; export default meta; type Story = StoryObj; export const LoggedIn: Story = { args: { ...HeaderStories.LoggedIn.args, }, }; ``` ```ts // Page.stories.ts|tsx — CSF Next 🧪 //👇 Imports all Header stories const meta = preview.meta({ component: Page, }); export const LoggedIn = meta.story({ args: { ...HeaderStories.LoggedIn.input.args, }, }); ``` ## Args can modify any aspect of your component You can use args in your stories to configure the component's appearance, similar to what you would do in an application. For example, here's how you could use a `footer` arg to populate a child component: ```tsx // Page.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. type PagePropsAndCustomArgs = React.ComponentProps & { footer?: string }; const meta = { component: Page, render: ({ footer, ...args }) => (
{footer}
), } satisfies Meta; export default meta; type Story = StoryObj; export const CustomFooter = { args: { footer: 'Built with Storybook', }, } satisfies Story; ``` ```tsx // Page.stories.ts|tsx — CSF Next 🧪 type PagePropsAndCustomArgs = React.ComponentProps & { footer?: string; }; const meta = preview.type<{ args: PagePropsAndCustomArgs }>().meta({ component: Page, render: ({ footer, ...args }) => (
{footer}
), }); export const CustomFooter = meta.story({ args: { footer: 'Built with Storybook', }, }); ``` ## Setting args through the URL You can also override the set of initial args for the active story by adding an `args` query parameter to the URL. Typically, you would use [Controls](https://storybook.js.org/docs/essentials/controls.md) to handle this. For example, here's how you could set a `size` and `style` arg in the Storybook's URL: ``` ?path=/story/avatar--default&args=style:rounded;size:100 ``` As a safeguard against [XSS](https://owasp.org/www-community/attacks/xss/) attacks, the arg's keys and values provided in the URL are limited to alphanumeric characters, spaces, underscores, and dashes. Any other types will be ignored and removed from the URL, but you can still use them with the Controls panel and [within your story](#mapping-to-complex-arg-values). The `args` param is always a set of `key: value` pairs delimited with a semicolon `;`. Values will be coerced (cast) to their respective `argTypes` (which may have been automatically inferred). Objects and arrays are supported. Special values `null` and `undefined` can be set by prefixing with a bang `!`. For example, `args=obj.key:val;arr[0]:one;arr[1]:two;nil:!null` will be interpreted as: ```js { obj: { key: 'val' }, arr: ['one', 'two'], nil: null } ``` Similarly, special formats are available for dates and colors. Date objects will be encoded as `!date(value)` with value represented as an ISO date string. Colors are encoded as `!hex(value)`, `!rgba(value)` or `!hsla(value)`. Note that rgb(a) and hsl(a) should not contain spaces or percentage signs in the URL. Args specified through the URL will extend and override any default values of args set on the story. ## Setting args from within a story Interactive components often need to be controlled by their containing component or page to respond to events, modify their state and reflect those changes in the UI. For example, when a user toggles a switch component, the switch should be checked, and the arg shown in Storybook should reflect the change. To enable this, you can use the `useArgs` API exported by `storybook/preview-api`: ```tsx // my-component/component.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { title: 'Inputs/Checkbox', component: Checkbox, } satisfies Meta; export default meta; type Story = StoryObj; export const Example = { args: { isChecked: false, label: 'Try Me!', }, /** * 👇 To avoid linting issues, it is recommended to use a function with a capitalized name. * If you are not concerned with linting, you may use an arrow function. */ render: function Render(args) { const [{ isChecked }, updateArgs] = useArgs(); function onChange() { updateArgs({ isChecked: !isChecked }); } return ; }, } satisfies Story; ``` ```tsx // my-component/component.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ title: 'Inputs/Checkbox', component: Checkbox, }); export const Example = meta.story({ args: { isChecked: false, label: 'Try Me!', }, /** * 👇 To avoid linting issues, it is recommended to use a function with a capitalized name. * If you are not concerned with linting, you may use an arrow function. */ render: function Render(args) { const [{ isChecked }, updateArgs] = useArgs(); function onChange() { updateArgs({ isChecked: !isChecked }); } return ; }, }); ``` If you're using Storybook's hooks API in the story's render function, **do not** mix them with React's hooks such as `useState`, `useEffect`, or `useRef`. This is because side effects and re-rendering triggered by React's hooks do not run through Storybook's hook context, which can cause an error on re-render. To manage state and side effects within a story, you must use Storybook's equivalent hooks, such as `useState`, `useEffect`, and `useRef` from `storybook/preview-api`. ## Mapping to complex arg values Complex values such as JSX elements cannot be serialized to the manager (e.g., the Controls panel) or synced with the URL. Arg values can be "mapped" from a simple string to a complex type using the `mapping` property in `argTypes` to work around this limitation. It works in any arg but makes the most sense when used with the `select` control type. ```ts // Example.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Example, argTypes: { label: { control: { type: 'select' }, options: ['Normal', 'Bold', 'Italic'], mapping: { Bold: Bold, Italic: Italic, }, }, }, } satisfies Meta; export default meta; ``` ```ts // Example.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Example, argTypes: { label: { control: { type: 'select' }, options: ['Normal', 'Bold', 'Italic'], mapping: { Bold: Bold, Italic: Italic, }, }, }, }); ``` Note that `mapping` does not have to be exhaustive. If the arg value is not a property of `mapping`, the value will be used directly. Keys in `mapping` always correspond to arg _values_, not their index in the `options` array.
Using args in addons If you are [writing an addon](https://storybook.js.org/docs/addons/writing-addons.md) that wants to read or update args, use the `useArgs` hook exported by `storybook/manager-api`: ```js // my-addon/src/manager.js|ts const [args, updateArgs, resetArgs] = useArgs(); // To update one or more args: updateArgs({ key: 'value' }); // To reset one (or more) args: resetArgs((argNames: ['key'])); // To reset all args resetArgs(); ```
> Source: https://storybook.js.org/docs/writing-stories/args --- # Parameters Parameters are a set of static, named metadata about a story, typically used to control the behavior of Storybook features and addons. Available parameters are listed in the [parameters API reference](https://storybook.js.org/docs/api/parameters.md#available-parameters). For example, let’s customize the backgrounds feature via a parameter. We’ll use `parameters.backgrounds` to define which backgrounds appear in the backgrounds toolbar when a story is selected. ## Story parameters We can set a parameter for a single story with the `parameters` key on a CSF export: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { // 👇 Story-level parameters parameters: { backgrounds: { options: { red: { name: 'Red', value: '#f00' }, green: { name: 'Green', value: '#0f0' }, blue: { name: 'Blue', value: '#00f' }, }, }, }, }; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ // 👇 Story-level parameters parameters: { backgrounds: { options: { red: { name: 'Red', value: '#f00' }, green: { name: 'Green', value: '#0f0' }, blue: { name: 'Blue', value: '#00f' }, }, }, }, }); ``` ## Component parameters We can set the parameters for all stories of a component using the `parameters` key on the default CSF export: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, //👇 Creates specific parameters at the component level parameters: { backgrounds: { options: {}, }, }, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, //👇 Creates specific parameters at the component level parameters: { backgrounds: { options: {}, }, }, }); ``` ## Global parameters We can also set the parameters for **all stories** via the `parameters` export of your [`.storybook/preview.ts|tsx`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file (this is the file where you configure all stories): ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { backgrounds: { options: { light: { name: 'Light', value: '#fff' }, dark: { name: 'Dark', value: '#333' }, }, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ parameters: { backgrounds: { options: { light: { name: 'Light', value: '#fff' }, dark: { name: 'Dark', value: '#333' }, }, }, }, }); ``` Setting a global parameter is a common way to configure addons. With backgrounds, you configure the list of backgrounds that every story can render in. ## Rules of parameter inheritance The way the global, component and story parameters are combined is: - More specific parameters take precedence (so a story parameter overwrites a component parameter which overwrites a global parameter). - Parameters are **merged**, so keys are only ever overwritten and never dropped. The merging of parameters is important. This means it is possible to override a single specific sub-parameter on a per-story basis while retaining most of the parameters defined globally. If you are defining an API that relies on parameters (e.g., an [**addon**](https://storybook.js.org/docs/addons.md)) it is a good idea to take this behavior into account. > Source: https://storybook.js.org/docs/writing-stories/parameters --- # Decorators A decorator is a way to wrap a story in extra “rendering” functionality. Many addons define decorators to augment your stories with extra rendering or gather details about how your story renders. When writing stories, decorators are typically used to wrap stories with extra markup or context mocking. ## Wrap stories with extra markup Some components require a “harness” to render in a useful way. For instance, if a component runs right up to its edges, you might want to space it inside Storybook. Use a decorator to add spacing for all stories of the component. ![Story without padding](../_assets/writing-stories/decorators-no-padding.png) ```tsx // YourComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: YourComponent, decorators: [ (Story) => (
), ], } satisfies Meta; export default meta; ``` ```tsx // YourComponent.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: YourComponent, decorators: [ (Story) => (
), ], }); ``` ![Story with padding](../_assets/writing-stories/decorators-padding.png) ## “Context” for mocking The second argument to a decorator function is the **story context** which contains the properties: - `args` - the story arguments. You can use some [`args`](https://storybook.js.org/docs/writing-stories/args.md) in your decorators and drop them in the story implementation itself. - `argTypes`- Storybook's [argTypes](https://storybook.js.org/docs/api/arg-types.md) allow you to customize and fine-tune your stories [`args`](https://storybook.js.org/docs/writing-stories/args.md). - `globals` - Storybook-wide [globals](https://storybook.js.org/docs/essentials/toolbars-and-globals.md#globals). In particular you can use the [toolbars feature](https://storybook.js.org/docs/essentials/toolbars-and-globals.md#global-types-and-the-toolbar-annotation) to allow you to change these values using Storybook’s UI. - `hooks` - Storybook's API hooks (e.g., `useArgs`, `useGlobals`). These are available in both decorators and story render functions. When using these hooks in a render function alongside framework hooks (e.g., React's `useState`, `useEffect`), use Storybook's hook equivalents from `storybook/preview-api` instead to avoid errors on re-render. - `parameters`- the story's static metadata, most commonly used to control Storybook's behavior of features and addons. - `viewMode`- Storybook's current active window (e.g., canvas, docs). This context can be used to adjust the behavior of your decorator based on the story's arguments or other metadata. For example, you could create a decorator that allows you to optionally apply a layout to the story, by defining `parameters.pageLayout = 'page'` (or `'page-mobile'`): : ```tsx // .storybook/preview.tsx — CSF 3 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) const preview: Preview = { decorators: [ // 👇 Defining the decorator in the preview file applies it to all stories (Story, { parameters }) => { // 👇 Make it configurable by reading from parameters const { pageLayout } = parameters; switch (pageLayout) { case 'page': return ( // Your page layout is probably a little more complex than this
); case 'page-mobile': return (
); default: // In the default case, don't apply a layout return ; } }, ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ decorators: [ // 👇 Defining the decorator in the preview file applies it to all stories (Story, { parameters }) => { // 👇 Make it configurable by reading from parameters const { pageLayout } = parameters; switch (pageLayout) { case 'page': return ( // Your page layout is probably a little more complex than this
); case 'page-mobile': return (
); default: // In the default case, don't apply a layout return ; } }, ], }); ``` For another example, see the section on [configuring the mock provider](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-providers.md#configuring-the-mock-provider), which demonstrates how to use the same technique to change which theme is provided to the component. ### Using decorators to provide data If your components are “connected” and require side-loaded data to render, you can use decorators to provide that data in a mocked way without having to refactor your components to take that data as an arg. There are several techniques to achieve this. Depending on exactly how you are loading that data. Read more in the [building pages in Storybook](https://storybook.js.org/docs/writing-stories/build-pages-with-storybook.md) section. ## Story decorators To define a decorator for a single story, use the `decorators` key on a named export: ```tsx // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { decorators: [ (Story) => (
), ], }; ``` ```tsx // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Primary = meta.story({ decorators: [ (Story) => (
), ], }); ``` It is useful to ensure that the story remains a “pure” rendering of the component under test and that any extra HTML or components are used only as decorators. In particular the [Source](https://storybook.js.org/docs/api/doc-blocks/doc-block-source.md) Doc Block works best when you do this. ## Component decorators To define a decorator for all stories of a component, use the `decorators` key of the default CSF export: ```tsx // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, decorators: [ (Story) => (
), ], } satisfies Meta; export default meta; ``` ```tsx // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, decorators: [ (Story) => (
), ], }); ``` ## Global decorators We can also set a decorator for **all stories** via the `decorators` export of your [`.storybook/preview.ts|tsx`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file (this is the file where you configure all stories): ```tsx // .storybook/preview.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const preview: Preview = { decorators: [ (Story) => (
), ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ decorators: [ (Story) => (
), ], }); ``` ## Decorator inheritance Like parameters, decorators can be defined globally, at the component level, and for a single story (as we’ve seen). All decorators relevant to a story will run in the following order once the story renders: - Global decorators, in the order they are defined - Component decorators, in the order they are defined - Story decorators, in the order they are defined, starting from the innermost decorator and working outwards and up the hierarchy in the same order > Source: https://storybook.js.org/docs/writing-stories/decorators --- # Play function `Play` functions are small snippets of code executed after the story renders. They enable you to interact with your components and test scenarios that otherwise require user intervention. ## Writing stories with the play function Storybook's `play` functions are small code snippets that run once the story finishes rendering. Aided by the [interactions panel](https://storybook.js.org/docs/writing-tests/interaction-testing.md#debugging-interaction-tests), it allows you to build component interactions and test scenarios that were impossible without user intervention. For example, if you were working on a registration form and wanted to validate it, you could write the following story with the `play` function: ```ts // RegistrationForm.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: RegistrationForm, } satisfies Meta; export default meta; type Story = StoryObj; /* * See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas * to learn more about using the canvas to query the DOM */ export const FilledForm: Story = { play: async ({ canvas, userEvent }) => { const emailInput = canvas.getByLabelText('email', { selector: 'input', }); await userEvent.type(emailInput, 'example-email@email.com', { delay: 100, }); const passwordInput = canvas.getByLabelText('password', { selector: 'input', }); await userEvent.type(passwordInput, 'ExamplePassword', { delay: 100, }); const submitButton = canvas.getByRole('button'); await userEvent.click(submitButton); }, }; ``` ```ts // RegistrationForm.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: RegistrationForm, }); /* * See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas * to learn more about using the canvas to query the DOM */ export const FilledForm = meta.story({ play: async ({ canvas, userEvent }) => { const emailInput = canvas.getByLabelText('email', { selector: 'input', }); await userEvent.type(emailInput, 'example-email@email.com', { delay: 100, }); const passwordInput = canvas.getByLabelText('password', { selector: 'input', }); await userEvent.type(passwordInput, 'ExamplePassword', { delay: 100, }); const submitButton = canvas.getByRole('button'); await userEvent.click(submitButton); }, }); ``` See the [interaction testing documentation](https://storybook.js.org/docs/writing-tests/interaction-testing.md#writing-interaction-tests) for an overview of the available API events. When Storybook finishes rendering the story, it executes the steps defined within the `play` function, interacting with the component and filling the form's information. All of this without the need for user intervention. If you check your `Interactions` panel, you'll see the step-by-step flow. ## Working with the canvas Part of the context passed to the `play` function is a `canvas` object. This object allows you to query the DOM of the rendered story. It provides a scoped version of the Testing Library queries, so you can use them as you would in a regular test. ```ts // MyComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: MyComponent, } satisfies Meta; export default meta; type Story = StoryObj; export const ExampleStory: Story = { play: async ({ canvas, userEvent }) => { // Starts querying from the component's root element await userEvent.type(canvas.getByTestId('example-element'), 'something'); await userEvent.click(canvas.getByRole('button')); }, }; ``` ```ts // MyComponent.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: MyComponent, }); export const ExampleStory = meta.story({ play: async ({ canvas, userEvent }) => { // Starts querying from the component's root element await userEvent.type(canvas.getByTestId('example-element'), 'something'); await userEvent.click(canvas.getByRole('button')); }, }); ``` If you need to query outside of the canvas (for example, to test a dialog that appears outside of the story root), you can use the `screen` object available from `storybook/test`. ```ts // Dialog.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Dialog, } satisfies Meta; export default meta; type Story = StoryObj; export const Open: Story = { play: async ({ canvas, userEvent }) => { await userEvent.click(canvas.getByRole('button', { name: 'Open dialog' })); // Starts querying from the document const dialog = screen.getByRole('dialog'); await expect(dialog).toBeVisible(); }, }; ``` ```ts // Dialog.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: Dialog, }); export const Open = meta.story({ play: async ({ canvas, userEvent }) => { await userEvent.click(canvas.getByRole('button', { name: 'Open dialog' })); // Starts querying from the document const dialog = screen.getByRole('dialog'); await expect(dialog).toBeVisible(); }, }); ``` ## Composing stories Thanks to the [Component Story Format](https://storybook.js.org/docs/api/csf.md), an ES6 module based file format, you can also combine your `play` functions, similar to other existing Storybook features (e.g., [args](https://storybook.js.org/docs/writing-stories/args.md)). For example, if you wanted to verify a specific workflow for your component, you could write the following stories: ```ts // MyComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: MyComponent, } satisfies Meta; export default meta; type Story = StoryObj; /* * See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas * to learn more about using the canvas to query the DOM */ export const FirstStory: Story = { play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByTestId('an-element'), 'example-value'); }, }; export const SecondStory: Story = { play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByTestId('other-element'), 'another value'); }, }; export const CombinedStories: Story = { play: async ({ context, canvas, userEvent }) => { // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); await SecondStory.play(context); await userEvent.type(canvas.getByTestId('another-element'), 'random value'); }, }; ``` ```ts // MyComponent.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: MyComponent, }); /* * See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas * to learn more about using the canvas to query the DOM */ export const FirstStory = meta.story({ play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByTestId('an-element'), 'example-value'); }, }); export const SecondStory = meta.story({ play: async ({ canvas, userEvent }) => { await userEvent.type(canvas.getByTestId('other-element'), 'another value'); }, }); export const CombinedStories = meta.story({ play: async ({ context, canvas, userEvent }) => { // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); await SecondStory.play(context); await userEvent.type(canvas.getByTestId('another-element'), 'random value'); }, }); ``` By combining the stories, you're recreating the entire component workflow and can spot potential issues while reducing the boilerplate code you need to write. > Source: https://storybook.js.org/docs/writing-stories/play-function --- # Loaders Loaders are asynchronous functions that load data for a story and its [decorators](https://storybook.js.org/docs/writing-stories/decorators.md). A story's loaders run before the story renders, and the loaded data injected into the story via its render context. Loaders can be used to load any asset, lazy load components, or fetch data from a remote API. This feature was designed as a performance optimization to handle large story imports. However, [args](https://storybook.js.org/docs/writing-stories/args.md) is the recommended way to manage story data. We're building up an ecosystem of tools and techniques around Args that might not be compatible with loaded data. They are an advanced feature (i.e., escape hatch), and we only recommend using them if you have a specific need that other means can't fulfill. ## Fetching API data Stories are isolated component examples that render internal data defined as part of the story or alongside the story as [args](https://storybook.js.org/docs/writing-stories/args.md). Loaders are helpful when you need to load story data externally (e.g., from a remote API). Consider the following example that fetches a todo item to display in a todo list: ```tsx // MyComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. /* *👇 Render functions are a framework specific feature to allow you control on how the component renders. * See https://storybook.js.org/docs/api/csf * to learn how to use render functions. */ const meta = { component: TodoItem, render: (args, { loaded: { todo } }) => , } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { loaders: [ async () => ({ todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), }), ], }; ``` ```tsx // MyComponent.stories.ts|tsx — CSF Next 🧪 /* *👇 Render functions are a framework specific feature to allow you control on how the component renders. * See https://storybook.js.org/docs/api/csf * to learn how to use render functions. */ const meta = preview.meta({ component: TodoItem, render: (args, { loaded: { todo } }) => , }); export const Primary = meta.story({ loaders: [ async () => ({ todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(), }), ], }); ``` The response obtained from the remote API call is combined into a `loaded` field on the story context, which is the second argument to a story function. For example, in React, the story's args were spread first to prioritize them over the static data provided by the loader. With other frameworks (e.g., Angular), you can write your stories as you'd usually do. ## Global loaders We can also set a loader for **all stories** via the `loaders` export of your [`.storybook/preview.js`](https://storybook.js.org/docs/configure/index.md#configure-story-rendering) file (this is the file where you configure all stories): ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { loaders: [ async () => ({ currentUser: await (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(), }), ], }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ loaders: [ async () => ({ currentUser: await (await fetch('https://jsonplaceholder.typicode.com/users/1')).json(), }), ], }); ``` In this example, we load a "current user" available as `loaded.currentUser` for all stories. ## Loader inheritance Like [parameters](https://storybook.js.org/docs/writing-stories/parameters.md), loaders can be defined globally, at the component level, and for a single story (as we’ve seen). All loaders, defined at all levels that apply to a story, run before the story renders in Storybook's canvas. - All loaders run in parallel - All results are the `loaded` field in the story context - If there are keys that overlap, "later" loaders take precedence (from lowest to highest): - Global loaders, in the order they are defined - Component loaders, in the order they are defined - Story loaders, in the order they are defined > Source: https://storybook.js.org/docs/writing-stories/loaders --- # Tags Tags allow you to control which stories are included in your Storybook, enabling many different uses of the same total set of stories. For example, you can use tags to include/exclude tests from the [test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md#run-tests-for-a-subset-of-stories). For more complex use cases, see the [recipes](#recipes) section, below. ## Built-in tags The following tags are available in every Storybook project: | Tag | Applied by default? | Description | | ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dev` | Yes | Stories tagged with `dev` are rendered in Storybook's sidebar. | | `manifest` | Yes | Stories and docs tagged with `manifest` are included in the [component or docs manifests](https://storybook.js.org/docs/ai/manifests.md) (Currently React-only). | | `test` | Yes | Stories tagged with `test` are included in [test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md#run-tests-for-a-subset-of-stories) or [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#including-excluding-or-skipping-tests) runs. | | `autodocs` | No | Stories tagged with `autodocs` are included in the [docs page](https://storybook.js.org/docs/writing-docs/autodocs.md). If a CSF file does not contain at least one story tagged with `autodocs`, that component will not generate a docs page. | | `play-fn` | No | Applied automatically to stories with a [play function](https://storybook.js.org/docs/writing-stories/play-function.md) defined. | | `test-fn` | No | Applied automatically to tests defined using the [experimental `.test` method on CSF Factories](https://storybook.js.org/docs/api/csf/csf-next.md#storytest). | The `dev`, `manifest`, and `test` tags are automatically, implicitly applied to every story in your Storybook project. ## Custom tags You're not limited to the built-in tags. Custom tags enable a flexible layer of categorization on top of Storybook's sidebar hierarchy. Sample uses might include: - Status, such as `experimental`, `new`, `stable`, or `deprecated` - User persona, such as `admin`, `user`, or `developer` - Component/code ownership There are two ways to create a custom tag: 1. Apply it to a story, component (meta), or project (`.storybook/preview.*`) as described below. 2. Define it in your Storybook configuration file (`.storybook/main.*`) to provide more configuration options, like default [filter selection](#filtering-the-sidebar-by-tags). For example, to define an "experimental" tag that is excluded by default in the sidebar, you can add this to your Storybook config: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], tags: { // 👇 Define a custom tag named "experimental" experimental: { defaultFilterSelection: 'exclude', // Or 'include' }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], tags: { // 👇 Define a custom tag named "experimental" experimental: { defaultFilterSelection: 'exclude', // Or 'include' }, }, }); ``` If `defaultFilterSelection` is set to `include`, stories with this tag are selected as included in the filter menu. If set to `exclude`, stories with this tag are selected as excluded, and must be explicitly included by selecting the tag in the sidebar filter menu. If not set, the tag has no default selection. You can also use the [`tags` configuration](https://storybook.js.org/docs/api/main-config/main-config-tags.md) to alter the configuration of built-in tags. ## Applying tags A tag can be any static (i.e. not created dynamically) string, either the [built-in tags](#built-in-tags) or [custom tags](#custom-tags) of your own design. To apply tags to a story, assign an array of strings to the `tags` property. Tags may be applied at the project, component (meta), or story levels. For example, to apply the `autodocs` tag to all stories in your project, you can use `.storybook/preview.*`: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { /* * All stories in your project will have these tags applied: * - autodocs * - dev (implicit default) * - test (implicit default) */ tags: ['autodocs'], }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ addons: [addonDocs()], /* * All stories in your project will have these tags applied: * - autodocs * - dev (implicit default) * - test (implicit default) */ tags: ['autodocs'], }); ``` Within a component stories file, you apply tags like so: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, /* * All stories in this file will have these tags applied: * - autodocs * - dev (implicit default, inherited from preview) * - test (implicit default, inherited from preview) */ tags: ['autodocs'], } satisfies Meta; export default meta; type Story = StoryObj; export const ExperimentalFeatureStory: Story = { /* * This particular story will have these tags applied: * - experimental * - autodocs (inherited from meta) * - dev (inherited from meta) * - test (inherited from meta) */ tags: ['experimental'], }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, /* * All stories in this file will have these tags applied: * - autodocs * - dev (implicit default, inherited from preview) * - test (implicit default, inherited from preview) */ tags: ['autodocs'], }); export const ExperimentalFeatureStory = meta.story({ /* * This particular story will have these tags applied: * - experimental * - autodocs (inherited from meta) * - dev (inherited from meta) * - test (inherited from meta) */ tags: ['experimental'], }); ``` ## Removing tags To remove a tag from a story, prefix it with `!`. For example: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, etc. const meta = { component: Button, // 👇 Applies to all stories in this file tags: ['stable'], } satisfies Meta; export default meta; type Story = StoryObj; export const ExperimentalFeatureStory: Story = { //👇 For this particular story, remove the inherited `stable` tag and apply the `experimental` tag tags: ['!stable', 'experimental'], }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, // 👇 Applies to all stories in this file tags: ['stable'], }); export const ExperimentalFeatureStory = meta.story({ //👇 For this particular story, remove the inherited `stable` tag and apply the `experimental` tag tags: ['!stable', 'experimental'], }); ``` Tags can be removed for all stories in your project (in `.storybook/preview.*`), all stories for a component (in the CSF file meta), or a single story (as above). ## Filtering the sidebar by tags Both built-in and custom tags are available as filters in Storybook's sidebar. Selecting a tag in the filter causes the sidebar to only show stories with that tag. Selecting multiple tags shows stories that contain any of those tags. Pressing the Exclude button for a tag in the filter menu excludes stories with that tag from the sidebar. You can exclude multiple tags, and stories with any of those tags will be excluded. You can also mix inclusion and exclusion. When no tags are selected, all stories are shown. In this example, the `experimental` tag has been excluded and the Documentation tag (`autodocs`) has been included, so only stories tagged with `autodocs` but not `experimental` are shown. ![Filtering by tags](../_assets/writing-stories/tag-filter.png) Filtering by tags is a powerful way to focus on a subset of stories, especially in large Storybook projects. When searching, the filter is applied first, so search results are limited to the currently filtered tags. ## Recipes ### Docs-only stories It can sometimes be helpful to provide example stories for documentation purposes, but you want to keep the sidebar navigation more focused on stories useful for development. By enabling the `autodocs` tag and removing the `dev` tag, a story becomes docs-only: appearing only in the [docs page](https://storybook.js.org/docs/writing-docs/autodocs.md) and not in Storybook's sidebar. ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, /* * All stories in this file will: * - Be included in the docs page * - Not appear in Storybook's sidebar */ tags: ['autodocs', '!dev'], } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, /* * All stories in this file will: * - Be included in the docs page * - Not appear in Storybook's sidebar */ tags: ['autodocs', '!dev'], }); ``` ### Combo stories, still tested individually For a component with many variants, like a Button, a grid of those variants all together can be a helpful way to visualize it. But you may wish to test the variants individually. You can accomplish this with tags like so: ```tsx // Button.stories.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Variant1: Story = { // 👇 This story will not appear in Storybook's sidebar or docs page tags: ['!dev', '!autodocs'], args: { variant: 1 }, }; export const Variant2: Story = { // 👇 This story will not appear in Storybook's sidebar or docs page tags: ['!dev', '!autodocs'], args: { variant: 2 }, }; export const Combo: Story = { // 👇 This story should not be tested, but will appear in the sidebar and docs page tags: ['!test'], render: () => ( <> ), }; ``` ```tsx // Button.stories.tsx — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const Variant1 = meta.story({ // 👇 This story will not appear in Storybook's sidebar or docs page tags: ['!dev', '!autodocs'], args: { variant: 1 }, }); export const Variant2 = meta.story({ // 👇 This story will not appear in Storybook's sidebar or docs page tags: ['!dev', '!autodocs'], args: { variant: 2 }, }); export const Combo = meta.story({ // 👇 This story should not be tested, but will appear in the sidebar and docs page tags: ['!test'], render: () => ( <> ), }); ``` ### Test cases that don't clutter the sidebar (⚠️ **Experimental**: While this API is available for all tags, the built-in `_test` tag is experimental) If you're using the [experimental `.test` method on CSF Factories](https://github.com/storybookjs/storybook/discussions/30119), you can alter the default behavior of the `_test` tag to exclude tests from the sidebar by default. This reduces clutter in the sidebar while still allowing you to run tests for all stories, or adjust the filter to show tests when needed. ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], tags: { // 👇 Adjust the default configuration of this tag _test: { defaultFilterSelection: 'exclude', }, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], tags: { // 👇 Adjust the default configuration of this tag _test: { defaultFilterSelection: 'exclude', }, }, }); ``` > Source: https://storybook.js.org/docs/writing-stories/tags --- # Naming components and hierarchy Storybook provides a powerful way to organize your stories, giving you the necessary tools to categorize, search, and filter your stories based on your organization's needs and preferences. ## Structure and hierarchy When organizing your Storybook, there are two methods of structuring your stories: **implicit** and **explicit**. The [implicit method](https://storybook.js.org/docs/configure/user-interface/sidebar-and-urls.md#csf-30-auto-titles) involves relying upon the physical location of your stories to position them in the sidebar, while the [explicit method](#naming-stories) involves utilizing the `title` parameter to place the story. ![Storybook sidebar hierarchy](../_assets/writing-stories/naming-hierarchy-sidebar-anatomy.png) Based on how you structure your Storybook, you can see that the story hierarchy is made up of various parts: - **Category**: The top-level grouping of stories and documentation pages generated by Storybook - **Folder**: A mid-level organizational unit that groups components and stories in the sidebar, representing a feature or section of your application - **Component**: A low-level organizational unit representing the component that the story is testing - **Docs**: The automatically generated [documentation page](https://storybook.js.org/docs/writing-docs/autodocs.md) for the component - **Story**: The individual story testing a specific component state ## Naming stories When creating your stories, you can explicitly use the `title` parameter to define the story's position in the sidebar. It can also be used to [group](#grouping) related components together in an expandable interface to help with Storybook organization providing a more intuitive experience for your users. For example: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Button', component: Button, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Button', component: Button, }); ``` Yields this: ![Stories hierarchy without paths](../_assets/writing-stories/naming-hierarchy-no-path.png) ## Grouping It is also possible to group related components in an expandable interface to help with Storybook organization. To do so, use the `/` as a separator: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Button', component: Button, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Button', component: Button, }); ``` ```ts // Checkbox.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Checkbox', component: Checkbox, } satisfies Meta; export default meta; ``` ```ts // Checkbox.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Checkbox', component: Checkbox, }); ``` Yields this: ![Stories hierarchy with paths](../_assets/writing-stories/naming-hierarchy-with-path.png) ## Roots By default, the top-level grouping will be displayed as “root” in the Storybook UI (i.e., the uppercased, non-expandable items). If you need, you can [configure Storybook](https://storybook.js.org/docs/configure/user-interface/sidebar-and-urls.md#roots) and disable this behavior. Useful if you need to provide a streamlined experience for your users; nevertheless, if you have a large Storybook composed of multiple component stories, we recommend naming your components according to the file hierarchy. ## Single-story hoisting Single-story components (i.e., component stories without **siblings**) whose **display name** exactly matches the component's name (last part of `title`) are automatically hoisted up to replace their parent component in the UI. For example: ```ts // Button.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Button', component: ButtonComponent, } satisfies Meta; export default meta; type Story = StoryObj; // This is the only named export in the file, and it matches the component name export const Button: Story = {}; ``` ```ts // Button.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ /* 👇 The title prop is optional. * See https://storybook.js.org/docs/configure/#configure-story-loading * to learn how to generate automatic titles */ title: 'Design System/Atoms/Button', component: ButtonComponent, }); // This is the only named export in the file, and it matches the component name export const Button = meta.story(); ``` ![Stories hierarchy with single story hoisting](../_assets/writing-stories/naming-hierarchy-single-story-hoisting.png) Because story exports are automatically "start cased" (`myStory` becomes `"My Story"`), your component name should match that. Alternatively, you can override the story name using `myStory.storyName = '...'` to match the component name. ## Sorting stories Out of the box, Storybook sorts stories based on the order in which they are imported. However, you can customize this pattern to suit your needs and provide a more intuitive experience by adding `storySort` to the `options` parameter in your `preview.js` file. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { options: { // The `a` and `b` arguments in this function have a type of `import('storybook/internal/types').IndexEntry`. Remember that the function is executed in a JavaScript environment, so use JSDoc for IntelliSense to introspect it. storySort: (a, b) => a.id === b.id ? 0 : a.id.localeCompare(b.id, undefined, { numeric: true }), }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ parameters: { options: { // The `a` and `b` arguments in this function have a type of `import('storybook/internal/types').IndexEntry`. Remember that the function is executed in a JavaScript environment, so use JSDoc for IntelliSense to introspect it. storySort: (a, b) => a.id === b.id ? 0 : a.id.localeCompare(b.id, undefined, { numeric: true }), }, }, }); ``` Asides from the unique story identifier, you can also use the `title`, `name`, and import path to sort your stories using the `storySort` function. The `storySort` can also accept a configuration object. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { options: { storySort: { method: '', order: [], locales: '', }, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ parameters: { options: { storySort: { method: '', order: [], locales: '', }, }, }, }); ``` | Field | Type | Description | Required | Default Value | Example | | ---------------- | ------- | -------------------------------------------------------- | -------- | ----------------------- | ------------------------- | | **method** | String | Tells Storybook in which order the stories are displayed | No | Storybook configuration | `'alphabetical'` | | **order** | Array | The stories to be shown, ordered by supplied name | No | Empty Array `[]` | `['Intro', 'Components']` | | **includeNames** | Boolean | Include story name in sort calculation | No | `false` | `true` | | **locales** | String | The locale required to be displayed | No | System locale | `en-US` | To sort your stories alphabetically, set `method` to `'alphabetical'` and optionally set the `locales` string. To sort your stories using a custom list, use the `order` array; stories that don't match an item in the `order` list will appear after the items in the list. The `order` array can accept a nested array to sort 2nd-level story kinds. For example: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { options: { storySort: { order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components'], }, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ parameters: { options: { storySort: { order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components'], }, }, }, }); ``` Which would result in this story ordering: 1. `Intro` and then `Intro/*` stories 2. `Pages` story 3. `Pages/Home` and `Pages/Home/*` stories 4. `Pages/Login` and `Pages/Login/*` stories 5. `Pages/Admin` and `Pages/Admin/*` stories 6. `Pages/*` stories 7. `Components` and `Components/*` stories 8. All other stories If you want specific categories to sort to the end of the list, you can insert a `*` into your `order` array to indicate where "all other stories" should go: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { options: { storySort: { order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components', '*', 'WIP'], }, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ parameters: { options: { storySort: { order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components', '*', 'WIP'], }, }, }, }); ``` In this example, the `WIP` category would be displayed at the end of the list. Note that the `order` option is independent of the `method` option; stories are sorted first by the `order` array and then by either the `method: 'alphabetical'` or the default `configure()` import order. > Source: https://storybook.js.org/docs/writing-stories/naming-components-and-hierarchy --- # Mocking modules Components often depend on other modules, such as other components, utility functions, or libraries. These can be from external packages or internal to your project. When rendering those components in Storybook or [testing](https://storybook.js.org/docs/writing-tests.md) them, you may want to mock those modules to control their behavior and isolate the component's functionality. For example, this simple component depends on two modules, a local utility function to access the user's browser session and an external package to generate a unique ID: ```jsx title="AuthButton.jsx" export function AuthButton() { const user = getUserFromSession(); const id = uuidv4(); return ( ); } ``` The above example is written with React, but the same principles apply to other renderers like Vue, Svelte, or Web Components. The important part is the usage of the two module dependencies. When writing stories or tests for this component, you may want to mock the `getUserFromSession` function to control the user data returned, or mock the `uuidv4` function to return a predictable ID. This allows you to test the component's behavior without relying on the actual implementations of these modules. For maximum flexibility, Storybook provides three ways to mock modules for your stories. Let's walk through each of them, starting with the most straightforward approach. ## Automocking Automocking is the most straightforward way to mock modules in Storybook, and we recommend it for all projects using the [Vite](https://storybook.js.org/docs/builders/vite.md) and [Webpack](https://storybook.js.org/docs/builders/webpack.md) builders (other builders must use one of the other techniques, below). This approach requires minimal configuration while allowing for flexible mocking of modules. It works with two steps. First, register the modules you want to mock in your Storybook configuration. Then, control the behavior and make assertions about the mocked modules in your stories. ### Registering modules to mock When automocking, you use the `sb.mock` utility function to register modules you want to mock. There are three ways to register modules: as spy-only, fully automocked, or with a mock file. Each method has its use cases and benefits. There are some key details to keep in mind when using the `sb.mock` utility: - You can register both local modules (e.g., `../lib/session.ts`) and packages in `node_modules` (e.g., `uuid`). - You can only register mocked modules in your project-level configuration: `.storybook/preview.*`. This ensures consistent and performant mocking across all stories in your project. You can modify the behavior of these modules in your stories, but you cannot register them directly in the story files. - When registering a mock for a local module, the path must: - Not use an alias or subpath import (e.g., `@/lib/session.ts` or `#lib/session`). - Be relative to the `.storybook/preview.*` file. - Include the file extension (e.g., `.ts` or `.js`). - If you are using Typescript, you can wrap the module path in `import()` to ensure the module is correctly resolved and typed. For example, `sb.mock(import('../lib/session.ts'))`. - If you are using the [Webpack builder](https://storybook.js.org/docs/builders/webpack.md), you can only automock `node_module` packages that have ESModules (ESM) entry points. If a module has both CommonJS (CJS) and ESM entry points, Webpack doesn't correctly resolve the ESM entry and it cannot be mocked. Webpack users can still mock CJS `node_module` packages by providing a [mock file](#mock-files). #### Spy-only For most cases, you should register a mocked module as spy-only, by setting the `spy` option to `true`. This leaves the original module's functionality intact, while still allowing you to modify the behavior if needed and make assertions in your tests. For example, if you want to spy on the `getUserFromSession` function and the `uuidv4` function from the `uuid` package, you can call the `sb.mock` utility function in your `.storybook/preview.*` file: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, vue3-vite, sveltekit) // 👇 Automatically spies on all exports from the `lib/session` local module sb.mock(import('../lib/session.ts'), { spy: true }); // 👇 Automatically spies on all exports from the `uuid` package in `node_modules` sb.mock(import('uuid'), { spy: true }); const preview: Preview = { // ... }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) // 👇 Automatically spies on all exports from the `lib/session` local module sb.mock(import('../lib/session.ts'), { spy: true }); // 👇 Automatically spies on all exports from the `uuid` package in `node_modules` sb.mock(import('uuid'), { spy: true }); export default definePreview({ // ... }); ``` If you need to mock an external module that has a deeper import path (e.g. `lodash-es/add`), register the mock with that path. You can then [control the behavior of these modules](#using-automocked-modules-in-stories) and make assertions about them in your stories, such as checking if a function was called or what arguments it was called with. #### Fully automocked modules For cases where you need to prevent the original module's functionality from executing, set the `spy` option to `false` (or omit it, because that is the default value). This will automatically replace all exports from the module with [Vitest mock functions](https://vitest.dev/api/mock.html), allowing you to control their behavior and make assertions while being certain that the original functionality never runs. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, vue3-vite, sveltekit) // 👇 Automatically replaces all exports from the `lib/session` local module with mock functions sb.mock(import('../lib/session.ts')); // 👇 Automatically replaces all exports from the `uuid` package in `node_modules` with mock functions sb.mock(import('uuid')); const preview: Preview = { // ... }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) // 👇 Automatically replaces all exports from the `lib/session` local module with mock functions sb.mock(import('../lib/session.ts')); // 👇 Automatically replaces all exports from the `uuid` package in `node_modules` with mock functions sb.mock(import('uuid')); export default definePreview({ // ... }); ``` Fully automocked modules do not execute their exported functions, but the module is still evaluated, along with its dependencies. This means that if the module has side effects (e.g., modifying global state, logging to the console, etc.), those side effects will still occur. Similarly, a module written to run on the server will attempt to be evaluated in the browser. If you want to prevent the original module's code from running entirely, you should use a [mock file](#mock-files) instead. You can then [control the behavior of these modules](#using-automocked-modules-in-stories) and make assertions about them in your stories, just like with the spy-only approach. #### Mock files If you want to mock a module with more complex behavior or reuse a mock's behavior across multiple stories, you can create a mock file. This file should be placed in a `__mocks__` directory next to the module you want to mock, and it should export the same named exports as the original module. For example, to mock the `session` module in the `lib` directory, create a file named `session.js|ts` in the `lib/__mocks__` directory: ```js title="lib/__mocks__/session.js" export function getUserFromSession() { return { name: 'Mocked User' }; } ``` For packages in your `node_modules`, create a `__mocks__` directory in the root of your project and create the mock file there. For example, to mock the `uuid` package, create a file named `uuid.js` in the `__mocks__` directory: ```js title="__mocks__/uuid.js" export function v4() { return '1234-5678-90ab-cdef'; } ``` If you need to mock an external module that has a deeper import path (e.g. `lodash-es/add`), create a corresponding mock file (e.g. `__mocks__/lodash-es/add.js`) in the root of your project. The root of your project is determined differently depending on your builder: **Vite projects** The root `__mocks__` directory should be placed in the [`root` directory](https://vite.dev/config/shared-options.html#root), as defined in your project's Vite configuration (typically `process.cwd()`) If that is unavailable, it defaults to the directory containing your `.storybook` directory. **Webpack projects** The root `__mocks__` directory should be placed in the [`context` directory](https://webpack.js.org/configuration/entry-context/#context), as defined in your project's Webpack configuration (typically `process.cwd()`). If that is unavailable, it defaults to the root of your repository. Mock files must be written with JavaScript (not TypeScript) using ESModules (not CJS). They must export the same named exports as the original module. If you want to mock a default export, you can use `export default` in the mock file. You can then use the `sb.mock` utility to register these mock files in your `preview.*` file: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, vue3-vite, sveltekit) // 👇 Replaces imports of this module with imports to `../lib/__mocks__/session.ts` sb.mock(import('../lib/session.ts')); // 👇 Replaces imports of this module with imports to `../__mocks__/uuid.ts` sb.mock(import('uuid')); const preview: Preview = { // ... }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) // 👇 Replaces imports of this module with imports to `../lib/__mocks__/session.ts` sb.mock(import('../lib/session.ts')); // 👇 Replaces imports of this module with imports to `../__mocks__/uuid.ts` sb.mock(import('uuid')); export default definePreview({ // ... }); ``` Note that the API for registering automatically mocked modules and mock files is the same. The only difference is that `sb.mock` will first look for a mock file in the appropriate directory before automatically mocking the module. ### Using automocked modules in stories All registered automocked modules are used the same way within your stories. You can control the behavior, such as defining what it returns, and make assertions about the modules. ```ts // AuthButton.stories.ts — CSF 3 // Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.) const meta = { component: AuthButton, // 👇 This will run before each story is rendered beforeEach: async () => { // 👇 Force known, consistent behavior for mocked modules mocked(uuidv4).mockReturnValue('1234-5678-90ab-cdef'); mocked(getUserFromSession).mockReturnValue({ name: 'John Doe' }); }, } satisfies Meta; export default meta; type Story = StoryObj; export const LogIn: Story = { play: async ({ canvas, userEvent }) => { const button = canvas.getByRole('button', { name: 'Sign in' }); userEvent.click(button); // Assert that the getUserFromSession function was called expect(getUserFromSession).toHaveBeenCalled(); }, }; ``` ```ts // AuthButton.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: AuthButton, // 👇 This will run before each story is rendered beforeEach: async () => { // 👇 Force known, consistent behavior for mocked modules mocked(uuidv4).mockReturnValue('1234-5678-90ab-cdef'); mocked(getUserFromSession).mockReturnValue({ name: 'John Doe' }); }, }); export const LogIn = meta.story({ play: async ({ canvas, userEvent }) => { const button = canvas.getByRole('button', { name: 'Sign in' }); userEvent.click(button); // Assert that the getUserFromSession function was called expect(getUserFromSession).toHaveBeenCalled(); }, }); ``` Mocked functions created with the `sb.mock` utility are full [Vitest mock functions](https://vitest.dev/api/mock.html), which means you can use all the methods available on them. Some of the most useful methods include: | Method | Description | | -------------------------------------------------------------------------------- | ----------------------------------------------------- | | [`mockReturnValue(value)`](https://vitest.dev/api/mock.html#mockreturnvalue) | Sets the return value of the mocked function. | | [`mockResolvedValue(value)`](https://vitest.dev/api/mock.html#mockresolvedvalue) | Sets the value the mocked async function resolves to. | | [`mockImplementation(fn)`](https://vitest.dev/api/mock.html#mockimplementation) | Sets a custom implementation for the mocked function. | If you are [writing your stories in TypeScript](https://storybook.js.org/docs/writing-stories/typescript.md), you can use the `mocked` utility from `storybook/test` to ensure that the mocked functions are correctly typed in your stories. This utility is a type-safe wrapper around the Vitest `vi.mocked` function. ### How it works Storybook's automocking is built on Vitest's mocking engine. The behavior adjusts depending on whether you're in development mode or build mode: **Dev Mode** In dev mode, mocking relies on Vite's module graph invalidation. When a mock is added, changed, or removed (either in `.storybook/preview.*` or the `__mocks__` directory), the plugin intelligently invalidates all affected modules and triggers a hot reload. This provides a fast and interactive development experience. **Dev and build mode** - Build-time analysis: A new Vite plugin, viteMockPlugin, scans `.storybook/preview.*` for all `sb.mock()` calls during the build process. - Mock Processing: - `__mocks__` redirects: If a corresponding file is found in the top-level `__mocks__` directory, that file is loaded and transformed by Vite. - Automocking & spies: If no `__mocks__` file is found, the original module's code is transformed at build-time to replace its exports with mocks or spies. - No runtime overhead: Because all mocking decisions and transformations happen at build time, there is no performance penalty or complex interception logic needed in the final built application. The mocked modules are directly bundled in place of the originals. #### Comparison to Vitest mocking While this feature uses Vitest's mocking engine, the implementation within Storybook has some key differences: - Scope: Mocks are global and defined only in `.storybook/preview.*`. Unlike Vitest, you cannot call `sb.mock()` inside individual story files. - Static by Design: All mocking decisions are finalized at build time. This makes the system robust and performant but less dynamic than Vitest's test-by-test mocking capabilities. There is no `sb.unmock()` or equivalent, as the module graph is fixed in a production build. - Runtime Mocking: While the module swap is static, you can still control the behavior of the mocked functions at runtime within a play function or `beforeEach` hook (e.g., `mocked(myFunction).mockReturnValue('new value')`). - No Factory Functions: The `sb.mock()` API does not accept a factory function as its second argument (e.g., `sb.mock('path', () => ({...}))`). This is because all mocking decisions are resolved at build time, whereas factories are executed at runtime. ## Alternative methods If [automocking](#automocking) is not suitable for your project, there are two alternative methods to mock modules in Storybook: [subpath imports](#subpath-imports) and [builder aliases](#builder-aliases). These methods require a bit more setup but provide similar functionality to automocking, allowing you to control the behavior of modules in your stories. ### Subpath imports You can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports), a Node feature, to mock modules. Subpath imports allow you to define custom paths for modules in your project, which can be used to replace the original module with a mock file. They work with both the [Vite](https://storybook.js.org/docs/builders/vite.md) and [Webpack](https://storybook.js.org/docs/builders/webpack.md) builders. #### Mock files To mock a module, create a file with the same name and in the same directory as the module you want to mock. For example, to mock a module named `session`, create a file next to it named `session.mock.js|ts`, with a few characteristics: - It must import the original module using a relative import. - Using a subpath or alias import would result in it importing itself. - It should re-export all exports from the original module. - It should use the `fn` utility to mock any necessary functionality from the original module. - It should use the [`mockName`](https://vitest.dev/api/mock.html#mockname) method to ensure the name is preserved when minified - It should not introduce side effects that could affect other tests or components. Mock files should be isolated and only affect the module they are mocking. Here's an example of a mock file for a module named `session`: ```ts // lib/session.mock.ts export * from './session'; export const getUserFromSession = fn(actual.getUserFromSession).mockName('getUserFromSession'); ``` When you use the `fn` utility to mock a module, you create full [Vitest mock functions](https://vitest.dev/api/mock.html). See [below](#using-mocked-modules-in-stories) for examples of how you can use a mocked module in your stories. **Mock files for external modules** You can't directly mock an external module like [`uuid`](https://github.com/uuidjs/uuid) or `node:fs`. Instead, you must wrap it in your own module, which you can mock like any other internal one. For example, with `uuid`, you could do the following: ```ts title="lib/uuid.ts" export const uuidv4 = v4; ``` And create a mock for the wrapper: ```ts title="lib/uuid.mock.ts" export const uuidv4 = fn(actual.uuidv4).mockName('uuidv4'); ``` #### Configuration To configure subpath imports, you define the `imports` property in your project's `package.json` file. This property maps the subpath to the actual file path. The example below configures subpath imports for four internal modules: ```jsonc // package.json { "imports": { "#api": { // storybook condition applies to Storybook "storybook": "./api.mock.ts", "default": "./api.ts", }, "#app/actions": { "storybook": "./app/actions.mock.ts", "default": "./app/actions.ts", }, "#lib/session": { "storybook": "./lib/session.mock.ts", "default": "./lib/session.ts", }, "#lib/db": { // test condition applies to test environments *and* Storybook "test": "./lib/db.mock.ts", "default": "./lib/db.ts", }, "#*": ["./*", "./*.ts", "./*.tsx"], }, } ``` There are three aspects to this configuration worth noting: First, **each subpath must begin with `#`**, to differentiate it from a regular module path. The `#*` entry is a catch-all that maps all subpaths to the root directory. Second, the order of the keys is important. The `default` key should come last. Third, note the **`storybook`, `test`, and `default` keys** in each module's entry. The `storybook` value is used to import the mock file when loaded in Storybook, while the `default` value is used to import the original module when loaded in your project. The `test` condition is also used within Storybook, which allows you to use the same configuration in Storybook and your other tests. With the package configuration in place, you can then update your component file to use the subpath import: ```ts title="AuthButton.ts" // ➖ Remove this line // import { getUserFromSession } from '../../lib/session'; // ➕ Add this line // ...rest of the file ``` Subpath imports will only be correctly resolved and typed when the [`moduleResolution` property](https://www.typescriptlang.org/tsconfig/#moduleResolution) is set to `'Bundler'`, `'NodeNext'`, or `'Node16'` in your TypeScript configuration. If you are currently using `'node'`, that is intended for projects using a Node.js version older than v10. Projects written with modern code likely do not need to use `'node'`. Storybook recommends the [TSConfig Cheat Sheet](https://www.totaltypescript.com/tsconfig-cheat-sheet) for guidance on setting up your TypeScript configuration. #### Using subpath imports in stories When you use the `fn` utility to mock a module, you create full [Vitest mock functions](https://vitest.dev/api/mock.html), which have many methods available. Some of the most useful methods include: | Method | Description | | -------------------------------------------------------------------------------- | ----------------------------------------------------- | | [`mockReturnValue(value)`](https://vitest.dev/api/mock.html#mockreturnvalue) | Sets the return value of the mocked function. | | [`mockResolvedValue(value)`](https://vitest.dev/api/mock.html#mockresolvedvalue) | Sets the value the mocked async function resolves to. | | [`mockImplementation(fn)`](https://vitest.dev/api/mock.html#mockimplementation) | Sets a custom implementation for the mocked function. | Here, we define `beforeEach` on a story (which will run before the story is rendered) to set a mocked return value for the `getUserFromSession` function used by the Page component: ```ts // Page.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. // 👇 Automocked module resolves to '../lib/__mocks__/session' const meta = { component: Page, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { async beforeEach() { // 👇 Set the return value for the getUserFromSession function mocked(getUserFromSession).mockReturnValue({ id: '1', name: 'Alice' }); }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 // 👇 Automocked module resolves to '../lib/__mocks__/session' const meta = preview.meta({ component: Page, }); export const Basic = meta.story({ async beforeEach() { // 👇 Set the return value for the getUserFromSession function mocked(getUserFromSession).mockReturnValue({ id: '1', name: 'Alice' }); }, }); ``` If you are [writing your stories in TypeScript](https://storybook.js.org/docs/writing-stories/typescript.md), you must import your mock modules using the full mocked file name to have the functions correctly typed in your stories. You do **not** need to do this in your component files. That's what the [subpath import](#subpath-imports) or [builder alias](#builder-aliases) is for. #### Spying on mocked modules The `fn` utility also spies on the original module's functions, which you can use to assert their behavior in your tests. For example, you can use [interaction tests](https://storybook.js.org/docs/writing-tests/interaction-testing.md) to verify that a function was called with specific arguments. For example, this story checks that the `saveNote` function was called when the user clicks the save button: ```ts // NoteUI.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. // 👇 Automocked module resolves to '../app/__mocks__/actions' const meta = { component: NoteUI } satisfies Meta; export default meta; type Story = StoryObj; const notes = createNotes(); export const SaveFlow: Story = { name: 'Save Flow ▶', args: { isEditing: true, note: notes[0], }, play: async ({ canvas, userEvent }) => { const saveButton = canvas.getByRole('menuitem', { name: /done/i }); await userEvent.click(saveButton); // 👇 This is the mock function, so you can assert its behavior await expect(saveNote).toHaveBeenCalled(); }, }; ``` ```ts // NoteUI.stories.ts — CSF Next 🧪 // 👇 Automocked module resolves to '../app/__mocks__/actions' const meta = preview.meta({ component: NoteUI }); const notes = createNotes(); export const SaveFlow = meta.story({ name: 'Save Flow ▶', args: { isEditing: true, note: notes[0], }, play: async ({ canvas, userEvent }) => { const saveButton = canvas.getByRole('menuitem', { name: /done/i }); await userEvent.click(saveButton); // 👇 This is the mock function, so you can assert its behavior await expect(saveNote).toHaveBeenCalled(); }, }); ``` ### Builder aliases If your project is unable to use [automocking](#automocking) or [subpath imports](#subpath-imports), you can configure your Storybook builder to alias the module to the [mock file](#mock-files-1). This will instruct the builder to replace the module with the mock file when bundling your Storybook stories. ```ts // .storybook/main.ts — Vite (CSF 3) // Replace your-framework with the framework you are using, e.g. react-vite, nextjs-vite, vue3-vite, etc. const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], viteFinal: async (config) => { if (config.resolve) { config.resolve.alias = { ...config.resolve?.alias, // 👇 External module lodash: import.meta.resolve('./lodash.mock'), // 👇 Internal modules '@/api': import.meta.resolve('./api.mock.ts'), '@/app/actions': import.meta.resolve('./app/actions.mock.ts'), '@/lib/session': import.meta.resolve('./lib/session.mock.ts'), '@/lib/db': import.meta.resolve('./lib/db.mock.ts'), }; } return config; }, }; export default config; ``` ```ts // .storybook/main.ts — Webpack (CSF 3) // Replace your-framework with the framework you are using (e.g., nextjs, react-webpack5) const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], webpackFinal: async (config) => { if (config.resolve) { config.resolve.alias = { ...config.resolve.alias, // 👇 External module lodash: import.meta.resolve('./lodash.mock'), // 👇 Internal modules '@/api$': import.meta.resolve('./api.mock.ts'), '@/app/actions$': import.meta.resolve('./app/actions.mock.ts'), '@/lib/session$': import.meta.resolve('./lib/session.mock.ts'), '@/lib/db$': import.meta.resolve('./lib/db.mock.ts'), }; } return config; }, }; export default config; ``` ```ts // .storybook/main.ts — Vite (CSF Next 🧪) // Replace your-framework with the framework you are using (e.g., react-vite, nextjs-vite) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], viteFinal: async (config) => { if (config.resolve) { config.resolve.alias = { ...config.resolve?.alias, // 👇 External module lodash: import.meta.resolve('./lodash.mock'), // 👇 Internal modules '@/api': import.meta.resolve('./api.mock.ts'), '@/app/actions': import.meta.resolve('./app/actions.mock.ts'), '@/lib/session': import.meta.resolve('./lib/session.mock.ts'), '@/lib/db': import.meta.resolve('./lib/db.mock.ts'), }; } return config; }, }); ``` ```ts // .storybook/main.ts — Webpack (CSF Next 🧪) // Replace your-framework with the framework you are using (e.g., nextjs, react-webpack5) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], webpackFinal: async (config) => { if (config.resolve) { config.resolve.alias = { ...config.resolve.alias, // 👇 External module lodash: import.meta.resolve('./lodash.mock'), // 👇 Internal modules '@/api$': import.meta.resolve('./api.mock.ts'), '@/app/actions$': import.meta.resolve('./app/actions.mock.ts'), '@/lib/session$': import.meta.resolve('./lib/session.mock.ts'), '@/lib/db$': import.meta.resolve('./lib/db.mock.ts'), }; } return config; }, }); ``` Usage of the aliased module in stories is similar to when [using subpath imports in stories](#using-subpath-imports-in-stories), but you import the module using the alias instead of the subpath. --- ## Common scenarios ### Setting up and cleaning up Before the story renders, you can use the asynchronous `beforeEach` function to perform any setup you need (e.g., configure the mock behavior). This function can be defined at the story, component (which will run for all stories in the file), or project (defined in `.storybook/preview.*`, which will run for all stories in the project). You can also return a cleanup function from `beforeEach` which will be called after your story unmounts. This is useful for tasks like unsubscribing observers, etc. It is _not_ necessary to restore `fn()` mocks with the cleanup function, as Storybook will already do that automatically before rendering a story. See the [`parameters.test.restoreMocks` API](https://storybook.js.org/docs/api/parameters.md#restoremocks) for more information. Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and reset it when the story unmounts. ```ts // Page.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Page, // 👇 Set the value of Date for every story in the file async beforeEach() { MockDate.set('2024-02-14'); // 👇 Reset the Date after each story return () => { MockDate.reset(); }; }, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { async play({ canvas }) { // ... This will run with the mocked Date }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Page, // 👇 Set the value of Date for every story in the file async beforeEach() { MockDate.set('2024-02-14'); // 👇 Reset the Date after each story return () => { MockDate.reset(); }; }, }); export const Basic = meta.story({ async play({ canvas }) { // ... This will run with the mocked Date }, }); ``` --- ## Troubleshooting ### Receiving an `exports is not defined` error Webpack projects may encounter an `exports is not defined` error when using [automocking](#automocking). This is usually caused by attempting to mock a module with CommonJS (CJS) entry points. Automocking with Webpack only works with modules that have ESModules (ESM) entry points exclusively, so you must use a [mock file](#mock-files) to mock CJS modules. ### Mocking conflicts with other testing tools If you've already set up mocking with other testing tools (e.g., [Jest](https://jestjs.io/)), you may encounter conflicts when using Storybook's mocking system. These conflicts can cause unexpected behavior, errors, or incorrect mocks when both tools try to mock the same module. This is a known issue when sharing mock files or configurations between Storybook and other testing tools. To address this situation, we recommend that you verify which tool is responsible for mocking a particular module and make sure the configurations do not overlap to avoid any conflicts. > Source: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules --- # Mocking network requests For components that make network requests (e.g. fetching data from a REST or GraphQL API), you can mock those requests using a tool like [Mock Service Worker (MSW)](https://mswjs.io/). MSW is an API mocking library, which relies on service workers to capture network requests and provides mocked data in response. The [MSW addon](https://storybook.js.org/addons/msw-storybook-addon/) brings this functionality into Storybook, allowing you to mock API requests in your stories. Below is an overview of how to set up and use the addon. ## Set up the MSW addon First, if necessary, run this command to install MSW and the MSW addon: ```sh npm install msw msw-storybook-addon --save-dev ``` ```sh pnpm add msw msw-storybook-addon --save-dev ``` ```sh yarn add msw msw-storybook-addon --save-dev ``` If you're not already using MSW, generate the service worker file necessary for MSW to work: ```shell npx msw init public/ ``` ```shell yarn dlx msw init public/ ``` ```shell pnpm dlx msw init public/ ``` Then ensure the [`staticDirs`](https://storybook.js.org/docs/api/main-config/main-config-static-dirs.md) property in your Storybook configuration will include the generated service worker file (in `/public`, by default): ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], staticDirs: ['../public', '../static'], }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], staticDirs: ['../public', '../static'], }); ``` Finally, initialize the addon and apply it to all stories with a [project-level loader](https://storybook.js.org/docs/writing-stories/loaders.md#global-loaders): ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. /* * Initializes MSW * See https://github.com/mswjs/msw-storybook-addon#configuring-msw * to learn how to customize it */ initialize(); const preview: Preview = { loaders: [mswLoader], // 👈 Add the MSW loader to all stories }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) /* * Initializes MSW * See https://github.com/mswjs/msw-storybook-addon#configuring-msw * to learn how to customize it */ initialize(); export default definePreview({ loaders: [mswLoader], // 👈 Add the MSW loader to all stories }); ``` ## Mocking REST requests If your component fetches data from a REST API, you can use MSW to mock those requests in Storybook. As an example, consider this document screen component: ```ts // YourPage.tsx // Example hook to retrieve data from an external endpoint function useFetchData() { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { setStatus('loading'); fetch('https://your-restful-endpoint') .then((res) => { if (!res.ok) { throw new Error(res.statusText); } return res; }) .then((res) => res.json()) .then((data) => { setStatus('success'); setData(data); }) .catch(() => { setStatus('error'); }); }, []); return { status, data, }; } export function DocumentScreen() { const { status, data } = useFetchData(); const { user, document, subdocuments } = data; if (status === 'loading') { return

Loading...

; } if (status === 'error') { return

There was an error fetching the data!

; } return ( ); } ``` This example uses the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to make network requests. If you're using a different library (e.g. [`axios`](https://axios-http.com/)), you can apply the same principles to mock network requests in Storybook. With the MSW addon, we can write stories that use MSW to mock the REST requests. Here's an example of two stories for the document screen component: one that fetches data successfully and another that fails. ```ts // YourPage.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: DocumentScreen, } satisfies Meta; export default meta; type Story = StoryObj; // 👇 The mocked data that will be used in the story const TestData = { user: { userID: 1, name: 'Someone', }, document: { id: 1, userID: 1, title: 'Something', brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', status: 'approved', }, subdocuments: [ { id: 1, userID: 1, title: 'Something', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', status: 'approved', }, ], }; export const MockedSuccess: Story = { parameters: { msw: { handlers: [ http.get('https://your-restful-endpoint/', () => { return HttpResponse.json(TestData); }), ], }, }, }; export const MockedError: Story = { parameters: { msw: { handlers: [ http.get('https://your-restful-endpoint', async () => { await delay(800); return new HttpResponse(null, { status: 403, }); }), ], }, }, }; ``` ```ts // YourPage.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: DocumentScreen, }); // 👇 The mocked data that will be used in the story const TestData = { user: { userID: 1, name: 'Someone', }, document: { id: 1, userID: 1, title: 'Something', brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', status: 'approved', }, subdocuments: [ { id: 1, userID: 1, title: 'Something', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', status: 'approved', }, ], }; export const MockedSuccess = meta.story({ parameters: { msw: { handlers: [ http.get('https://your-restful-endpoint/', () => { return HttpResponse.json(TestData); }), ], }, }, }); export const MockedError = meta.story({ parameters: { msw: { handlers: [ http.get('https://your-restful-endpoint', async () => { await delay(800); return new HttpResponse(null, { status: 403, }); }), ], }, }, }); ``` ## Mocking GraphQL requests GraphQL is another common way to fetch data in components. You can use MSW to mock GraphQL requests in Storybook. Here's an example of a document screen component that fetches data from a GraphQL API: ```ts // YourPage.tsx const AllInfoQuery = gql` query AllInfo { user { userID name } document { id userID title brief status } subdocuments { id userID title content status } } `; interface Data { allInfo: { user: { userID: number; name: string; opening_crawl: boolean; }; document: { id: number; userID: number; title: string; brief: string; status: string; }; subdocuments: { id: number; userID: number; title: string; content: string; status: string; }; }; } function useFetchInfo() { const { loading, error, data } = useQuery(AllInfoQuery); return { loading, error, data }; } export function DocumentScreen() { const { loading, error, data } = useFetchInfo(); if (loading) { return

Loading...

; } if (error) { return

There was an error fetching the data!

; } return ( ); } ``` This example uses GraphQL with [Apollo Client](https://www.apollographql.com/docs/) to make network requests. If you're using a different library (e.g. [URQL](https://formidable.com/open-source/urql/) or [React Query](https://react-query.tanstack.com/)), you can apply the same principles to mock network requests in Storybook. The MSW addon allows you to write stories that use MSW to mock the GraphQL requests. Here's an example demonstrating two stories for the document screen component. The first story fetches data successfully, while the second story fails. ```tsx // YourPage.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const mockedClient = new ApolloClient({ uri: 'https://your-graphql-endpoint', cache: new InMemoryCache(), defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', errorPolicy: 'all', }, query: { fetchPolicy: 'no-cache', errorPolicy: 'all', }, }, }); //👇The mocked data that will be used in the story const TestData = { user: { userID: 1, name: 'Someone', }, document: { id: 1, userID: 1, title: 'Something', brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', status: 'approved', }, subdocuments: [ { id: 1, userID: 1, title: 'Something', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', status: 'approved', }, ], }; const meta = { component: DocumentScreen, decorators: [ (Story) => ( ), ], } satisfies Meta; export default meta; type Story = StoryObj; export const MockedSuccess: Story = { parameters: { msw: { handlers: [ graphql.query('AllInfoQuery', () => { return HttpResponse.json({ data: { allInfo: { ...TestData, }, }, }); }), ], }, }, }; export const MockedError: Story = { parameters: { msw: { handlers: [ graphql.query('AllInfoQuery', async () => { await delay(800); return HttpResponse.json({ errors: [ { message: 'Access denied', }, ], }); }), ], }, }, }; ``` ```tsx // YourPage.stories.ts|tsx — CSF Next 🧪 const mockedClient = new ApolloClient({ uri: 'https://your-graphql-endpoint', cache: new InMemoryCache(), defaultOptions: { watchQuery: { fetchPolicy: 'no-cache', errorPolicy: 'all', }, query: { fetchPolicy: 'no-cache', errorPolicy: 'all', }, }, }); //👇The mocked data that will be used in the story const TestData = { user: { userID: 1, name: 'Someone', }, document: { id: 1, userID: 1, title: 'Something', brief: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', status: 'approved', }, subdocuments: [ { id: 1, userID: 1, title: 'Something', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', status: 'approved', }, ], }; const meta = preview.meta({ component: DocumentScreen, decorators: [ (Story) => ( ), ], }); export const MockedSuccess = meta.story({ parameters: { msw: { handlers: [ graphql.query('AllInfoQuery', () => { return HttpResponse.json({ data: { allInfo: { ...TestData, }, }, }); }), ], }, }, }); export const MockedError = meta.story({ parameters: { msw: { handlers: [ graphql.query('AllInfoQuery', async () => { await delay(800); return HttpResponse.json({ errors: [ { message: 'Access denied', }, ], }); }), ], }, }, }); ``` ## Configuring MSW for stories In the examples above, note how each story is configured with `parameters.msw` to define the request handlers for the mock server. Because it uses parameters in this way, it can also be configured at the [component](https://storybook.js.org/docs/writing-stories/parameters.md#component-parameters) or even [project](https://storybook.js.org/docs/writing-stories/parameters.md#global-parameters) level, allowing you to share the same mock server configuration across multiple stories. > Source: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-network-requests --- # Mocking providers The [context provider pattern](https://react.dev/learn/passing-data-deeply-with-context) and how to mock it only applies to renderers that use JSX, like [React](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/?renderer=react.md) or [Solid](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/?renderer=solid.md). Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. To mock a provider, you can wrap your component in a [decorator](https://storybook.js.org/docs/writing-stories/decorators.md) that includes the necessary context. ```tsx // .storybook/preview.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const preview: Preview = { decorators: [ (Story) => ( ), ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ decorators: [ (Story) => ( ), ], }); ``` Note the file extension above (`.tsx` or `.jsx`). You may need to adjust your preview file's extension to allow use of JSX, depending on your project's settings. For another example, reference the [Screens](https://storybook.js.org/tutorials/intro-to-storybook/react/en/screen/) chapter of the Intro to Storybook tutorial, where we mock a Redux provider with mock data. ## Configuring the mock provider When mocking a provider, it may be necessary to configure the provider to supply a different value for individual stories. For example, you might want to test a component with different themes or user roles. One way to do this is to define the decorator for each story individually. But if you imagine a scenario where you wish to create stories for each of your components in both light and dark themes, this approach can quickly become cumbersome. For a better way, with much less repetition, you can use the [decorator function's second "context" argument](https://storybook.js.org/docs/writing-stories/decorators.md#context-for-mocking) to access a story's [`parameters`](https://storybook.js.org/docs/writing-stories/parameters.md) and adjust the provided value. This way, you can define the provider once and adjust its value for each story. For example, we can adjust the decorator from above to read from `parameters.theme` to determine which theme to provide: ```tsx // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. // themes = { light, dark } const preview: Preview = { decorators: [ // 👇 Defining the decorator in the preview file applies it to all stories (Story, { parameters }) => { // 👇 Make it configurable by reading the theme value from parameters const { theme = 'light' } = parameters; return ( ); }, ], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) // themes = { light, dark } export default definePreview({ decorators: [ // 👇 Defining the decorator in the preview file applies it to all stories (Story, { parameters }) => { // 👇 Make it configurable by reading the theme value from parameters const { theme = 'light' } = parameters; return ( ); }, ], }); ``` Now, you can define a `theme` parameter in your stories to adjust the theme provided by the decorator: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; // Wrapped in light theme export const Basic: Story = {}; // Wrapped in dark theme export const Dark: Story = { parameters: { theme: 'dark', }, }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, }); // Wrapped in light theme export const Basic = meta.story(); // Wrapped in dark theme export const Dark = meta.story({ parameters: { theme: 'dark', }, }); ``` This powerful approach allows you to provide any value (theme, user role, mock data, etc.) to your components in a way that is both flexible and maintainable. > Source: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-providers --- # Building pages with Storybook Storybook helps you build any component, from small “atomic” components to composed pages. But as you move up the component hierarchy toward the page level, you deal with more complexity. There are many ways to build pages in Storybook. Here are common patterns and solutions. - Pure presentational pages. - Connected components (e.g., network requests, context, browser environment). ## Pure presentational pages Teams at the BBC, The Guardian, and the Storybook maintainers themselves build pure presentational pages. If you take this approach, you don't need to do anything special to render your pages in Storybook. It's straightforward to write components to be fully presentational up to the screen level. That makes it easy to show in Storybook. The idea is that you do all the messy “connected” logic in a single wrapper component in your app outside of Storybook. You can see an example of this approach in the [Data](https://storybook.js.org/tutorials/intro-to-storybook/react/en/data/) chapter of the Intro to Storybook tutorial. The benefits: - Easy to write stories once components are in this form. - All the data for the story is encoded in the args of the story, which works well with other parts of Storybook's tooling (e.g. [controls](https://storybook.js.org/docs/essentials/controls.md)). The downsides: - Your existing app may not be structured in this way, and it may be difficult to change it. - Fetching data in one place means that you need to drill it down to the components that use it. This can be natural in a page that composes one big GraphQL query (for instance), but other data fetching approaches may make this less appropriate. - It's less flexible if you want to load data incrementally in different places on the screen. ### Args composition for presentational screens When you are building screens in this way, it is typical that the inputs of a composite component are a combination of the inputs of the various sub-components it renders. For instance, if your screen renders a page layout (containing details of the current user), a header (describing the document you are looking at), and a list (of the subdocuments), the inputs of the screen may consist of the user, document and subdocuments. ```tsx // YourPage.ts|tsx export interface DocumentScreenProps { user?: {}; document?: Document; subdocuments?: SubDocuments[]; } export function DocumentScreen({ user, document, subdocuments }: DocumentScreenProps) { return ( ); } ``` In such cases, it is natural to use [args composition](https://storybook.js.org/docs/writing-stories/args.md#args-composition) to build the stories for the page based on the stories of the sub-components: ```ts // YourPage.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. // 👇 Imports the required stories const meta = { component: DocumentScreen, } satisfies Meta; export default meta; type Story = StoryObj; export const Simple: Story = { args: { user: PageLayout.Simple.args.user, document: DocumentHeader.Simple.args.document, subdocuments: DocumentList.Simple.args.documents, }, }; ``` ```ts // YourPage.stories.ts|tsx — CSF Next 🧪 // 👇 Imports the required stories const meta = preview.meta({ component: DocumentScreen, }); export const Simple = meta.story({ args: { user: PageLayout.Simple.input.args.user, document: DocumentHeader.Simple.input.args.document, subdocuments: DocumentList.Simple.input.args.documents, }, }); ``` This approach is beneficial when the various subcomponents export a complex list of different stories. You can pick and choose to build realistic scenarios for your screen-level stories without repeating yourself. Your story maintenance burden is minimal by reusing the data and taking a Don't-Repeat-Yourself(DRY) philosophy. ## Mocking connected components Connected components are components that depend on external data or services. For example, a full page component is often a connected component. When you render a connected component in Storybook, you need to mock the data or modules that the component depends on. There are various layers in which you can do that. ### [Mocking imports](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md) Components can depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to mock those modules to control their behavior. ### [Mocking API Services](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-network-requests.md) For components that make network requests (e.g., fetching data from a REST or GraphQL API), you can mock those requests in your stories. ### [Mocking providers](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-providers.md) Components can receive data or configuration from context providers. For example, a styled component might access its theme from a ThemeProvider or Redux uses React context to provide components access to app data. You can mock a provider and the value it's providing and wrap your component with it in your stories. ### Avoiding mocking dependencies It's possible to avoid mocking the dependencies of connected "container" components entirely by passing them around via props or React context. However, it requires a strict split of the container and presentational component logic. For example, if you have a component responsible for data fetching logic and rendering DOM, it will need to be mocked as previously described. It’s common to import and embed container components amongst presentational components. However, as we discovered earlier, we’ll likely have to mock their dependencies or the imports to render them within Storybook. Not only can this quickly grow to become a tedious task, but it’s also challenging to mock container components that use local states. So, instead of importing containers directly, a solution to this problem is to create a React context that provides the container components. It allows you to freely embed container components as usual, at any level in the component hierarchy without worrying about subsequently mocking their dependencies; since we can swap out the containers themselves with their mocked presentational counterpart. We recommend dividing context containers up over specific pages or views in your app. For example, if you had a `ProfilePage` component, you might set up a file structure as follows: ``` ProfilePage.js ProfilePage.stories.js ProfilePageContainer.js ProfilePageContext.js ``` It’s also often helpful to set up a “global” container context (perhaps named `GlobalContainerContext`) for container components that may be rendered on every page of your app and add them to the top level of your application. While it’s possible to place every container within this global context, it should only provide globally required containers. Let’s look at an example implementation of this approach. First, create a React context, and name it `ProfilePageContext`. It does nothing more than export a React context: ```js // ProfilePageContext.js|jsx const ProfilePageContext = createContext(); export default ProfilePageContext; ``` `ProfilePage` is our presentational component. It will use the `useContext` hook to retrieve the container components from `ProfilePageContext`: ```js // ProfilePage.js|jsx export const ProfilePage = ({ name, userId }) => { const { UserPostsContainer, UserFriendsContainer } = useContext(ProfilePageContext); return (

{name}

); }; ``` #### Mocking containers in Storybook In the context of Storybook, instead of providing container components through context, we’ll instead provide their mocked counterparts. In most cases, the mocked versions of these components can often be borrowed directly from their associated stories. ```js // ProfilePage.stories.js|jsx //👇 Imports a specific story from a story file export default { component: ProfilePage, }; const ProfilePageProps = { name: 'Jimi Hendrix', userId: '1', }; const context = { //👇 We can access the `userId` prop here if required: UserPostsContainer({ userId }) { return ; }, // Most of the time we can simply pass in a story. // In this case we're passing in the `normal` story export // from the `UserFriends` component stories. UserFriendsContainer: UserFriendsNormal, }; export const Normal = { render: () => ( ), }; ``` If the same context applies to all `ProfilePage` stories, we can use a [decorator](https://storybook.js.org/docs/writing-stories/decorators.md). #### Providing containers to your application Now, in the context of your application, you’ll need to provide `ProfilePage` with all of the container components it requires by wrapping it with `ProfilePageContext.Provider`: For example, in Next.js, this would be your `pages/profile.js` component. ```js // pages/profile.js|jsx //👇 Ensure that your context value remains referentially equal between each render. const context = { UserPostsContainer, UserFriendsContainer, }; export const AppProfilePage = () => { return ( ); }; ``` #### Mocking global containers in Storybook If you’ve set up `GlobalContainerContext`, you’ll need to set up a decorator within Storybook’s `preview.js` to provide context to all stories. For example: ```tsx // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const context = { NavigationContainer: NavigationNormal, }; const AppDecorator = (storyFn) => { return ( {storyFn()} ); }; const preview: Preview = { decorators: [AppDecorator], }; export default preview; ``` ```tsx // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) const context = { NavigationContainer: NavigationNormal, }; const AppDecorator = (storyFn) => { return ( {storyFn()} ); }; export default definePreview({ decorators: [AppDecorator], }); ``` > Source: https://storybook.js.org/docs/writing-stories/build-pages-with-storybook --- # Stories for multiple components It's useful to write stories that [render two or more components](https://storybook.js.org/docs/writing-stories/index.md#stories-for-two-or-more-components) at once if those components are designed to work together. For example, `ButtonGroup`, `List`, and `Page` components. ## Subcomponents When the components you're documenting have a parent-child relationship, you can use the `subcomponents` property to document them together. This is especially useful when the child component is not meant to be used on its own, but only as part of the parent component. Here's an example with `List` and `ListItem` components: ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: List, subcomponents: { ListItem }, //👈 Adds the ListItem component as a subcomponent } satisfies Meta; export default meta; type Story = StoryObj; export const Empty: Story = {}; export const OneItem: Story = { render: (args) => ( ), }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: List, subcomponents: { ListItem }, //👈 Adds the ListItem component as a subcomponent }); export const Empty = meta.story(); export const OneItem = meta.story({ render: (args) => ( ), }); ``` Note that by adding a `subcomponents` property to the meta (or default export), we get an extra panel on the [ArgTypes](https://storybook.js.org/docs/writing-docs/doc-blocks.md#argtypes) and [Controls](https://storybook.js.org/docs/essentials/controls.md) tables, listing the props of `ListItem`: ![Subcomponents in ArgTypes doc block](../_assets/writing-stories/doc-block-arg-types-subcomponents-for-list.png) Subcomponents are only intended for documentation purposes and have some limitations: 1. The [argTypes](https://storybook.js.org/docs/api/arg-types.md) of subcomponents are [inferred (for the renderers that support that feature)](https://storybook.js.org/docs/api/arg-types.md#automatic-argtype-inference) and cannot be manually defined or overridden. 2. The table for each documented subcomponent does _not_ include [controls](https://storybook.js.org/docs/essentials/controls.md) to change the value of the props, because controls always apply to the main component's args. Let's talk about some techniques you can use to mitigate the above, which are especially useful in more complicated situations. ## Reusing story definitions We can also reduce repetition in our stories by reusing story definitions. Here, we can reuse the `ListItem` stories' args in the story for `List`: ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 We're importing the necessary stories from ListItem const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; export const ManyItems: Story = { render: (args) => ( ), }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 //👇 We're importing the necessary stories from ListItem const meta = preview.meta({ component: List, }); export const ManyItems = meta.story({ render: (args) => ( ), }); ``` By rendering the `Unchecked` story with its args, we are able to reuse the input data from the `ListItem` stories in the `List`. However, we still aren’t using args to control the `ListItem` stories, which means we cannot change them with controls and we cannot reuse them in other, more complex component stories. ## Using children as an arg One way we improve that situation is by pulling the rendered subcomponent out into a `children` arg: ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 Instead of importing ListItem, we import the stories const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; export const OneItem: Story = { args: { children: , }, }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 //👇 Instead of importing ListItem, we import the stories const meta = preview.meta({ component: List, }); export const OneItem = meta.story({ args: { children: , }, }); ``` Now that `children` is an arg, we can potentially reuse it in another story. However, there are some caveats when using this approach that you should be aware of. The `children` arg, just like all args, needs to be JSON serializable. To avoid errors with your Storybook, you should: - Avoid using empty values - Use [mapping](https://storybook.js.org/docs/essentials/controls.md#dealing-with-complex-values) if you want to adjust the value with [controls](https://storybook.js.org/docs/essentials/controls.md) - Use caution with components that include third party libraries We're currently working on improving the overall experience for the children arg and allow you to edit children arg in a control and allow you to use other types of components in the near future. But for now you need to factor in this caveat when you're implementing your stories. ## Creating a Template Component Another option that is more “data”-based is to create a special “story-generating” template component: ```tsx // List.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. //👇 Imports a specific story from ListItem stories const meta = { component: List, } satisfies Meta; export default meta; type Story = StoryObj; //👇 The ListTemplate construct will be spread to the existing stories. const ListTemplate: Story = { render: ({ items, ...args }) => { return ( {items.map((item) => ( ))} ); }, }; export const Empty = { ...ListTemplate, args: { items: [], }, }; export const OneItem = { ...ListTemplate, args: { items: [{ ...Unchecked.args }], }, }; ``` ```tsx // List.stories.ts|tsx — CSF Next 🧪 //👇 Imports a specific story from ListItem stories const meta = preview.meta({ component: List, }); export const Empty = meta.story({ render: ({ items, ...args }) => { return ( {items.map((item) => ( ))} ); }, args: { items: [], }, }); export const OneItem = Empty.extend({ args: { items: [{ ...Unchecked.input.args }], }, }); ``` This approach requires more setup, but it means you can reuse the `args` to each story in a composite component. It also means you can alter the args to the component with the [Controls panel](https://storybook.js.org/docs/essentials/controls.md). > Source: https://storybook.js.org/docs/writing-stories/stories-for-multiple-components --- # Writing stories in TypeScript Writing your stories in [TypeScript](https://www.typescriptlang.org/) makes you more productive. You don't have to jump between files to look up component props. Your code editor will alert you about missing required props and even autocomplete prop values, just like when using your components within your app. Plus, Storybook infers those component types to auto-generate the [Controls](https://storybook.js.org/docs/api/doc-blocks/doc-block-controls.md) table. Storybook has built-in TypeScript support, so you can get started with zero configuration required. [CSF Next](https://storybook.js.org/docs/api/csf/csf-next.md) (currently in preview) provides significantly improved TypeScript support, which infers component types automatically and requires no explicit typing for most cases. We recommend using CSF Next for all new TypeScript stories. You only need to add types when using [custom args](#typing-custom-args). ## Typing stories with `Meta` and `StoryObj` When writing stories, there are two aspects that are helpful to type. The first is the [component meta](https://storybook.js.org/docs/writing-stories/index.md#default-export), which describes and configures the component and its stories. In a [CSF file](https://storybook.js.org/docs/api/csf.md), this is the default export. The second is the [stories themselves](https://storybook.js.org/docs/writing-stories/index.md#defining-stories). Storybook provides utility types for each of these, named `Meta` and `StoryObj`. Here's an example CSF file using those types: ```ts // Button.stories.ts // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic = {} satisfies Story; export const Primary = { args: { primary: true, }, } satisfies Story; ``` ### Props type parameter `Meta` and `StoryObj` types are both [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html#working-with-generic-type-variables), so you can provide them with an optional prop type parameter for the component type or the component's props type (e.g., the `typeof Button` portion of `Meta`). By doing so, TypeScript will prevent you from defining an invalid arg, and all [decorators](https://storybook.js.org/docs/writing-stories/decorators.md), [play functions](https://storybook.js.org/docs/writing-stories/play-function.md), or [loaders](https://storybook.js.org/docs/writing-stories/loaders.md) will type their function arguments. The example above passes a component type. See [**Typing custom args**](#typing-custom-args) for an example of passing a props type. ### Using `satisfies` for better type safety We are not yet able to provide additional type safety using the `satisfies` operator with Angular and Web components.
More info Both Angular and Web components utilize a class plus decorator approach. The decorators provide runtime metadata, but do not offer metadata at compile time. As a result, it appears impossible to determine if a property in the class is a required property or an optional property (but non-nullable due to a default value) or a non-nullable internal state variable. For more information, please refer to [this discussion](https://github.com/storybookjs/storybook/discussions/20988).
If you are using TypeScript 4.9+, you can take advantage of the new [`satisfies`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html) operator to get stricter type checking. Now you will receive type errors for missing required args, not just invalid ones. Using `satisfies` to apply a story's type helps maintain type safety when sharing a [play function](https://storybook.js.org/docs/writing-stories/play-function.md) across stories. Without it, TypeScript will throw an error that the `play` function may be undefined. The `satisfies` operator enables TypeScript to infer whether the play function is defined or not. Finally, use of `satisfies` allows you to pass `typeof meta` to the `StoryObj` generic. This informs TypeScript of the connection between the `meta` and `StoryObj` types, which allows it to infer the `args` type from the `meta` type. In other words, TypeScript will understand that args can be defined both at the story and meta level and won't throw an error when a required arg is defined at the meta level, but not at the story level. ## Typing custom args Sometimes stories need to define args that aren’t included in the component's props. For this case, you can use an [intersection type](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types) to combine a component's props type and your custom args' type. For example, here's how you could use a `footer` arg to populate a child component: ```tsx // Page.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. type PagePropsAndCustomArgs = React.ComponentProps & { footer?: string }; const meta = { component: Page, render: ({ footer, ...args }) => (
{footer}
), } satisfies Meta; export default meta; type Story = StoryObj; export const CustomFooter = { args: { footer: 'Built with Storybook', }, } satisfies Story; ``` ```tsx // Page.stories.ts|tsx — CSF Next 🧪 type PagePropsAndCustomArgs = React.ComponentProps & { footer?: string; }; const meta = preview.type<{ args: PagePropsAndCustomArgs }>().meta({ component: Page, render: ({ footer, ...args }) => (
{footer}
), }); export const CustomFooter = meta.story({ args: { footer: 'Built with Storybook', }, }); ``` > Source: https://storybook.js.org/docs/writing-stories/typescript --- # How to test UIs with Storybook Storybook [stories](https://storybook.js.org/docs/writing-stories.md) are test cases for your UI components in their various states and configurations. With Storybook, you can develop and test your components at the same time, in multiple ways, with instant feedback. Storybook tackles UI component testing from a holistic perspective, ensuring that you can not only execute component tests quickly and reliably, but that you also have well-established patterns to develop, debug, maintain, and even collaborate on these tests across the development lifecycle. ## Get started If your project is using Vite, you can likely use the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) to run your component tests in Storybook. This addon is built on top of [Vitest](https://vitest.dev/), a fast and lightweight test runner that works seamlessly with Vite. Run this command, which will install and configure the Vitest addon and Vitest: ```shell npx storybook add @storybook/addon-vitest ``` ```shell pnpm exec storybook add @storybook/addon-vitest ``` ```shell yarn exec storybook add @storybook/addon-vitest ``` The full installation instructions, including project requirements, are available in the [Vitest addon documentation](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#install-and-set-up). Once your project is set up, you will see a testing widget in the bottom of your sidebar. After running tests, you will also see test status indicators on sidebar items. Additionally, many tests can be debugged with an addon panel. ![Storybook app with story status indicators, testing widget, and interactions panel annotated](../_assets/writing-tests/testing-ui-overview.png) If you cannot use the Vitest addon in your project, you can still run tests using the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md). Next, we’ll cover some key concepts of testing in Storybook. ## Key concepts Testing in Storybook is similar to other testing environments. Most of the knowledge and techniques you’ve been using apply here, too. But there are also some significant improvements. ### Component tests A component test is a test which: - Renders a component in the browser for high fidelity - Simulates a user interacting with actual UI, like an end-to-end (E2E) test - Only tests a unit (e.g. a single component) of UI, **and** can reach into the implementation to mock things or manipulate data, like a unit test This combination of using a real browser, simulating behavior, and mocking provides a powerful means of testing the functional aspects of your UI. In Storybook, the entire testing experience is built around component tests. This means that you can run your tests in the same environment as your stories, with the same tools and techniques. ### Storybook Test Storybook Test enables real-time testing of your stories, through the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md). It uses a Vitest plugin to automatically transform your stories into real Vitest tests, which are then run with Vitest’s browser mode. ### Watch mode Get instant test feedback as you develop with watch mode. It will watch your code—either the component source or the tests—for changes and automatically re-run only the relevant tests. It’s perfect for test-driven development, where you write your tests first and then the component. To activate watch mode, press the watch mode button (the eye icon) in the testing widget: ![Testing widget with watch mode enabled](../_assets/writing-tests/test-widget-watch-mode-enabled.png) ### CI If you’re not running Storybook Test as part of your CI, you’re missing out on the biggest benefit it provides: catching bugs on PRs before you merge them. If you are already running `vitest` as part of your CI then your stories should automatically run as tests “for free” when you commit your changes to Git. If you’re not yet running Vitest in CI, you should set that up. First by adding a new script to your `package.json`: ```json title="package.json" { "scripts": { "test-storybook": "vitest --project=storybook" } } ``` Note that this assumes you have a Vitest project called “storybook” for your stories, which is the default configuration when you install Storybook Test. If you’ve renamed it, adjust the script accordingly. Next, add a new CI workflow.
If you use Github Actions that would look like: ```yaml title=".github/workflows/test-storybook.yml" name: Storybook Tests on: [push] jobs: test: runs-on: ubuntu-latest container: # Make sure to grab the latest version of the Playwright image # https://playwright.dev/docs/docker#pull-the-image image: mcr.microsoft.com/playwright:v1.58.2-noble steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22.12.0 - name: Install dependencies run: npm ci - name: Run tests run: npm run test-storybook ```
If you are using a different CI provider, please consult our full [CI guide](https://storybook.js.org/docs/writing-tests/in-ci.md) for more information. Storybook Test uses Playwright to render your stories by default. For the fastest experience, you should use [a machine image that has Playwright already installed](https://playwright.dev/docs/docker#pull-the-image) (as in the snippet above). ### Coverage Code coverage provides insight into which code is tested or not. It functions as a guardrail as you develop, to help be sure your work is covered by the proper tests. In Storybook Test, you can see the coverage provided by your stories in two ways. First, a summary is displayed in the testing widget. Second, clicking that coverage summary will open a full, interactive report. ![Two browser windows. The frontmost one shows the interactive coverage report generated by the Vitest addon. The background one shows the Storybook sidebar with the coverage summary visible in the testing widget.](../_assets/writing-tests/coverage-summary-and-report.png) In the report, clicking through to a specific component shows the exact branches of code that are covered or not covered: ![Interactive coverage report generated by the Vitest addon, showing the Page component's reported source](../_assets/writing-tests/coverage-report-html-component.png) Each project’s coverage report will look different, but the important things to note are: 1. **The overall line/branch coverage**, which serves as a high-level health check. 2. **Specific lines/branches** that are not covered, which are potential test gaps. In our point of view, the goal is not to get 100% coverage and fill every gap, but rather to have a barometer to help you judge code/test changes, and for the gaps to inform you of key states or interactions that are untested. For example, if you’re building a prototype you might skip testing altogether, whereas in a critical app you might want to dot every i and cross every t. Running coverage analysis can slow down your test runs, so it is turned off by default. To activate coverage, check the coverage checkbox in the testing widget. ![Testing widget with coverage activated](../_assets/writing-tests/test-widget-coverage-enabled.png) #### Coverage in CI And while we’re looking at coverage, update your CI workflow to include it: ```diff - yarn test + yarn test --coverage ``` Why are we running all tests (`yarn test`) instead of just the Storybook tests (`yarn test-storybook`)? Because a coverage report is most accurate when accounting for all tests in your project, not just the stories you've written. Seeing [Storybook-specific coverage](https://storybook.js.org/docs/writing-tests/test-coverage.md#storybook-ui) can be helpful, but in CI output, you want to see the comprehensive coverage of your project. This way we can track the coverage change in every PR. Those are the key concepts you’ll need to test in Storybook. Now, let’s look at the different types of tests you can perform. ## Types of tests Storybook Test supports a variety of testing types to help you validate your work in multiple dimensions. ### Render tests The most important tool for testing your components in Storybook is stories that render your components in various states. However, you might not be aware that a basic story is also a [smoke test](https://storybook.js.org/docs/writing-tests/), which we call a **render test**. The test passes when the story renders successfully and fails when it errors. ![Storybook app showing a failing render test](../_assets/writing-tests/interaction-test-failure-render.png) Depending on the complexity of your components, you might be able to capture many of your key UI states this way. ### [Interaction tests](https://storybook.js.org/docs/writing-tests/interaction-testing.md) Render tests are a very basic kind of interaction test. To test stateful components or verify interactive behavior, you define a play function for your story: ```ts // Dialog.stories.ts — CSF 3 // Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.) const meta = { component: Dialog, } satisfies Meta; export default meta; type Story = StoryObj; export const Opens: Story = { play: async ({ canvas, userEvent }) => { // Click on a button and assert that a dialog appears const button = canvas.getByRole('button', { name: 'Open Modal' }); await userEvent.click(button); await expect(canvas.getByRole('dialog')).toBeInTheDocument(); }, }; ``` ```ts // Dialog.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Dialog, }); export const Opens = meta.story({ play: async ({ canvas, userEvent }) => { // Click on a button and assert that a dialog appears const button = canvas.getByRole('button', { name: 'Open Modal' }); await userEvent.click(button); await expect(canvas.getByRole('dialog')).toBeInTheDocument(); }, }); ``` But `play` functions can also be used for setting up state, creating spies, mocking out the network, simulating user interactions with your components, asserting output, and more. They are the meat and potatoes of testing and are the foundation for the rest of your testing journey in Storybook. Here’s a more complex example, which includes [spying and mocking](https://storybook.js.org/docs/writing-tests/interaction-testing.md#spying-on-functions-with-fn) via the `fn` utility. ```ts // EventForm.stories.ts — CSF 3 // Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.) const meta = { component: EventForm, } satisfies Meta; export default meta; type Story = StoryObj; export const Submits: Story = { // Mock functions so we can manipulate and spy on them args: { getUsers: fn(), onSubmit: fn(), }, beforeEach: async ({ args }) => { // Manipulate `getUsers` mock to return mocked value args.getUsers.mockResolvedValue(users); }, play: async ({ args, canvas, userEvent }) => { const usersList = canvas.getAllByRole('listitem'); await expect(usersList).toHaveLength(4); await expect(canvas.getAllByText('VIP')).toHaveLength(2); const titleInput = await canvas.findByLabelText('Enter a title for your event'); await userEvent.type(titleInput, 'Holiday party'); const submitButton = canvas.getByRole('button', { name: 'Plan event' }); await userEvent.click(submitButton); // Spy on `onSubmit` to verify that it is called correctly await expect(args.onSubmit).toHaveBeenCalledWith({ name: 'Holiday party', userCount: 4, data: expect.anything(), }); }, }; ``` ```ts // EventForm.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: EventForm, }); export const Submits = meta.story({ // Mock functions so we can manipulate and spy on them args: { getUsers: fn(), onSubmit: fn(), }, beforeEach: async ({ args }) => { // Manipulate `getUsers` mock to return mocked value args.getUsers.mockResolvedValue(users); }, play: async ({ args, canvas, userEvent }) => { const usersList = canvas.getAllByRole('listitem'); await expect(usersList).toHaveLength(4); await expect(canvas.getAllByText('VIP')).toHaveLength(2); const titleInput = await canvas.findByLabelText('Enter a title for your event'); await userEvent.type(titleInput, 'Holiday party'); const submitButton = canvas.getByRole('button', { name: 'Plan event' }); await userEvent.click(submitButton); // Spy on `onSubmit` to verify that it is called correctly await expect(args.onSubmit).toHaveBeenCalledWith({ name: 'Holiday party', userCount: 4, data: expect.anything(), }); }, }); ``` For more information on how to write interaction tests and mocks using the `play` function, please see the [interaction testing guide](https://storybook.js.org/docs/writing-tests/interaction-testing.md#writing-interaction-tests). ### [Accessibility tests](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) Storybook’s [Accessibility (A11y) addon](https://storybook.js.org/addons/@storybook/addon-a11y) runs a set of automated checks on your stories to help ensure your components can be used by all users, regardless of ability or technology they're using. That means supporting requirements such as: keyboard navigation, screen reader support, usable color contrast, etc. Accessibility is not only the right thing to do, but it is increasingly mandated. For example, the [European accessibility act](https://ec.europa.eu/social/main.jsp?catId=1202) is scheduled to go into law in June 2025. Similarly in the US, laws like the [Americans with Disabilities Act (ADA)](https://www.ada.gov/) and [Section 508 of the Rehabilitation Act](https://www.section508.gov/) apply to many public-facing services. To activate accessibility checks alongside your component tests, check the Accessibility checkbox in the testing widget. ![Testing widget with accessibility activated](../_assets/writing-tests/test-widget-a11y-enabled.png) Once activated, you will see accessibility failures in the sidebar. ![Storybook showing a failing accessibility test in both the sidebar story menu and the Accessibility panel](../_assets/writing-tests/test-a11y-overview.png) Any failures can be debugged in the Accessibility addon panel. ![Storybook app with accessibility panel open, showing violations and an interactive popover on the violating elements in the preview](../_assets/writing-tests/addon-a11y-debug-violations.png) ### [Visual tests](https://storybook.js.org/docs/writing-tests/visual-testing.md) Visual tests are the most efficient way to test your components. With the click of a button you can take snapshots of every story in your Storybook and compare those snapshots to baselines — last known “good” snapshots. Not only does this allow you to check the appearance of your components, but they are also able to check a large subset of component functionality [without having to write or maintain any test code](https://storybook.js.org/blog/visual-testing-is-the-greatest-trick-in-ui-development/)! Storybook supports cross-browser visual testing natively using [Chromatic](https://www.chromatic.com/storybook/?ref=storybook_site), a cloud service made by the Storybook team. When you enable visual testing, every story is automatically turned into a test. This gives you instant feedback on UI bugs directly in Storybook. ![Visual test panel with diff](../_assets/writing-tests/vta-changes-found.png) ### [Snapshot tests](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) In most cases, the other testing types will provide more coverage with less effort. But there are scenarios where it can be helpful to compare the rendered markup of a story against a known baseline. For example, it can help identify markup changes that trigger rendering errors. ## Reusing stories in other testing tools Stories are made to be reusable, so you can use them as test cases in popular testing tools. ### [End-to-end](https://storybook.js.org/docs/writing-tests/integrations/stories-in-end-to-end-tests.md) Sometimes you need to test a full workflow, with the full running stack. In those cases, you can still use your stories by importing them within your Playwright or Cypress end-to-end (E2E) tests. ### [Unit](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md) If you prefer, you can reuse your Storybook stories in a traditional testing environment, like Vitest or Jest. **More testing resources** - [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) for running tests in Storybook - [Interaction testing](https://storybook.js.org/docs/writing-tests/interaction-testing.md) for user behavior simulation - [Accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) for accessibility - [Visual testing](https://storybook.js.org/docs/writing-tests/visual-testing.md) for appearance - [Snapshot testing](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) for rendering errors and warnings - [Test coverage](https://storybook.js.org/docs/writing-tests/test-coverage.md) for measuring code coverage - [CI](https://storybook.js.org/docs/writing-tests/in-ci.md) for running tests in your CI/CD pipeline - [End-to-end testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-end-to-end-tests.md) for simulating real user scenarios - [Unit testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md) for functionality - [Test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md) to automate test execution > Source: https://storybook.js.org/docs/writing-tests --- # Interaction tests In Storybook, interaction tests are built as part of a [story](https://storybook.js.org/docs/writing-stories.md). That story renders the component with the necessary props and context to place it in an initial state. You then use a [play function](https://storybook.js.org/docs/writing-stories/play-function.md) to simulate user behavior like clicks, typing, and submitting a form and then assert on the end result. You can preview and debug your interaction tests using the Interactions panel in the Storybook UI. They can be automated using the Vitest addon, allowing you to run tests for your project in Storybook, terminal, or CI environments. ## Writing interaction tests Every story you write can be render tested. A **render test** is a simple version of an interaction test that only tests the ability of a component to render successfully in a given state. That works fine for relatively simple, static components like a Button. But for more complex, interactive components, you can go farther. You can simulate user behavior and assert on functional aspects like DOM structure or function calls using the `play` function. When a component is tested, the play function is ran and any assertions within it are validated. In this example, the EmptyForm story tests the render of the LoginForm component and the FilledForm story tests the form submission: ```ts // LoginForm.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: LoginForm, } satisfies Meta; export default meta; type Story = StoryObj; export const EmptyForm: Story = {}; export const FilledForm: Story = { play: async ({ canvas, userEvent }) => { // 👇 Simulate interactions with the component await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); // See https://storybook.js.org/docs/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel await userEvent.click(canvas.getByRole('button')); // 👇 Assert DOM structure await expect( canvas.getByText( 'Everything is perfect. Your account is ready and we should probably get you started!', ), ).toBeInTheDocument(); }, }; ``` ```ts // LoginForm.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: LoginForm, }); export const EmptyForm = meta.story(); export const FilledForm = meta.story({ play: async ({ canvas, userEvent }) => { // 👇 Simulate interactions with the component await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); // See https://storybook.js.org/docs/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel await userEvent.click(canvas.getByRole('button')); // 👇 Assert DOM structure await expect( canvas.getByText( 'Everything is perfect. Your account is ready and we should probably get you started!', ), ).toBeInTheDocument(); }, }); ``` ![Storybook with a LoginForm component and passing interactions in the Interactions panel](../_assets/writing-tests/interaction-test-pass.png) There’s a lot going on in that code sample, so let’s walk through the APIs one-by-one. ### Querying the `canvas` `canvas` is a queryable element containing the story under test, which is provided as a parameter of your play function. You can use `canvas` to find specific elements to interact with or assert on. All query methods come directly from Testing Library and take the form of ``. The available types are summarized in this table and fully documented in the [Testing Library docs](https://testing-library.com/docs/queries/about#types-of-queries): | Type of Query | 0 Matches | 1 Match | >1 Matches | Awaited | | --------------------- | ------------- | -------------- | ------------ | ------- | | **Single Element** | | | | | | `getBy...` | Throw error | Return element | Throw error | No | | `queryBy...` | Return `null` | Return element | Throw error | No | | `findBy...` | Throw error | Return element | Throw error | Yes | | **Multiple Elements** | | | | | | `getAllBy...` | Throw error | Return array | Return array | No | | `queryAllBy...` | Return `[]` | Return array | Return array | No | | `findAllBy...` | Throw error | Return array | Return array | Yes | The subjects are listed here, with links to their full Testing Library documentation: 1. [`ByRole`](https://testing-library.com/docs/queries/byrole) — Find elements by their accessible role 2. [`ByLabelText`](https://testing-library.com/docs/queries/bylabeltext) — Find elements by their associated label text 3. [`ByPlaceholderText`](https://testing-library.com/docs/queries/byplaceholdertext) — Find elements by their placeholder value 4. [`ByText`](https://testing-library.com/docs/queries/bytext) — Find elements by the text they contain 5. [`ByDisplayValue`](https://testing-library.com/docs/queries/bydisplayvalue) — Find `input`, `textarea`, or `select` elements by their current value 6. [`ByAltText`](https://testing-library.com/docs/queries/byalttext) — Find elements with the given `alt` attribute value 7. [`ByTitle`](https://testing-library.com/docs/queries/bytitle) — Find elements with the given `title` attribute value 8. [`ByTestId`](https://testing-library.com/docs/queries/bytestid) — Find elements with the given `data-testid` attribute value Note the order of this list! We (and [Testing Libary](https://testing-library.com/docs/queries/about#priority)) highly encourage you to query for elements in a way that resembles the way a real person would interact with your UI. For example, finding elements by their accessible role helps ensure that the most people can use your component. While using `data-testid` should be a last resort, only after trying every other approach. Putting that altogether, some typical queries might look like: ```js // Find the first element with a role of button with the accessible name "Submit" await canvas.findByRole('button', { name: 'Submit' }); // Get the first element with the text "An example heading" canvas.getByText('An example heading'); // Get all elements with the role of listitem canvas.getAllByRole('listitem'); ``` ### Simulating behavior with `userEvent` After querying for elements, you will likely need to interact with them to test your component’s behavior. For this we use the `userEvent` utility, which is provided as a parameter of your play function. This utility simulates user interactions with the component, such as clicking buttons, typing in inputs, and selecting options. There are many methods available on `userEvent`, which are detailed in the [`user-event` documentation](https://testing-library.com/docs/user-event/intro#writing-tests-with-userevent). This table will highlight some of the commonly-used methods. | Method | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | | `click` | Clicks the element, calling a click() function
`await userEvent.click()` | | `dblClick` | Clicks the element twice
`await userEvent.dblClick()` | | `hover` | Hovers an element
`await userEvent.hover()` | | `unhover` | Unhovers out of element
`await userEvent.unhover()` | | `tab` | Presses the tab key
`await userEvent.tab()` | | `type` | Writes text inside inputs or textareas
`await userEvent.type(, 'Some text');` | | `keyboard` | Simulates keyboard events
`await userEvent.keyboard('{Shift}');` | | `selectOptions` | Selects the specified option(s) of a select element
`await userEvent.selectOptions(, ['1','2']);` | | `deselectOptions` | Removes the selection from a specific option of a select element
`await userEvent.deselectOptions(, '1');` | | `clear` | Selects the text inside inputs or textareas and deletes it
`await userEvent.clear();` | `userEvent` methods should _always_ be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. ### Asserting with `expect` Finally, after querying for elements and simulating behavior, you can make assertions on the result which are validated when running the test. For this we use the `expect` utility, which is available via the `storybook/test` module: ```js ``` The `expect` utility here combines the methods available in [Vitest’s `expect`](https://vitest.dev/api/expect.html) as well as those from [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom#custom-matchers) (which, despite the name, also work in Vitest tests). There are many, many methods available. This table will highlight some of the commonly-used methods. | Method | Description | | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | [`toBeInTheDocument()`](https://github.com/testing-library/jest-dom#tobeinthedocument) | Checks if the element is in the DOM
`await expect().toBeInTheDocument()` | | [`toBeVisible()`](https://github.com/testing-library/jest-dom#tobevisible) | Checks if the element is visible to the user
`await expect().toBeVisible()` | | [`toHaveAttribute()`](https://github.com/testing-library/jest-dom#tohaveattribute) | Checks if an element has an attribute
`await expect().toHaveAttribute('aria-disabled', 'true')` | | [`toHaveBeenCalled()`](https://vitest.dev/api/expect.html#tohavebeencalled) | Checks that a spied function was called
`await expect().toHaveBeenCalled()` | | [`toHaveBeenCalledWith()`](https://vitest.dev/api/expect.html#tohavebeencalledwith) | Checks that a spied function was called with specific parameters
`await expect().toHaveBeenCalledWith('example')` | `expect` calls should _always_ be `await`ed inside of the play function. This ensures they can be properly logged and debugged in the Interactions panel. ### Spying on functions with `fn` When your component calls a function, you can spy on that function to make assertions on its behavior using the `fn` utility from Vitest, available via the `storybook/test` module: ```js ``` Most of the time, you will use `fn` as an `arg` value when writing your story, then access that `arg` in your test: ```ts // LoginForm.stories.ts — CSF 3 // Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.) const meta = { component: LoginForm, args: { // 👇 Use `fn` to spy on the onSubmit arg onSubmit: fn(), }, } satisfies Meta; export default meta; type Story = StoryObj; export const FilledForm: Story = { play: async ({ args, canvas, userEvent }) => { await userEvent.type(canvas.getByLabelText('Email'), 'email@provider.com'); await userEvent.type(canvas.getByLabelText('Password'), 'a-random-password'); await userEvent.click(canvas.getByRole('button', { name: 'Log in' })); // 👇 Now we can assert that the onSubmit arg was called await expect(args.onSubmit).toHaveBeenCalled(); }, }; ``` ```ts // LoginForm.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: LoginForm, args: { // 👇 Use `fn` to spy on the onSubmit arg onSubmit: fn(), }, }); export const FilledForm = meta.story({ play: async ({ args, canvas, userEvent }) => { await userEvent.type(canvas.getByLabelText('Email'), 'email@provider.com'); await userEvent.type(canvas.getByLabelText('Password'), 'a-random-password'); await userEvent.click(canvas.getByRole('button', { name: 'Log in' })); // 👇 Now we can assert that the onSubmit arg was called await expect(args.onSubmit).toHaveBeenCalled(); }, }); ``` ### Run code before the component gets rendered You can execute code before rendering by using the `mount` function in the `play` method. Here's an example of using the [`mockdate`](https://github.com/boblauer/MockDate) package to mock the [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date), a useful way to make your story render in a consistent state. ```ts // Page.stories.ts — CSF 3 // ...rest of story file export const ChristmasUI: Story = { async play({ mount }) { MockDate.set('2024-12-25'); // 👇 Render the component with the mocked date await mount(); // ...rest of test }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Page, }); export const ChristmasUI = meta.story({ async play({ mount }) { MockDate.set('2024-12-25'); // 👇 Render the component with the mocked date await mount(); // ...rest of test }, }); ``` There are two requirements to use the `mount` function: 1. You _must_ destructure the mount property from the `context` (the argument passed to your play function). This makes sure that Storybook does not start rendering the story before the play function begins. 2. Your Storybook framework or builder must be configured to transpile to ES2017 or newer. This is because destructuring statements and async/await usages are otherwise transpiled away, which prevents Storybook from recognizing your usage of `mount`. #### Create mock data before rendering You can also use `mount` to create mock data that you want to pass to the component. To do so, first create your data in the play function and then call the `mount` function with a component configured with that data. In this example, we create a mock `note` and pass its `id` to the Page component, which we call `mount` with. ```tsx // Page.stories.tsx — CSF 3 // Replace your-framework with the framework you are using, e.g., react-vite, nextjs, nextjs-vite, etc. // 👇 Automocked module resolves to '../lib/__mocks__/db' const meta = { component: Page } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { play: async ({ mount, args, userEvent }) => { const note = await db.note.create({ data: { title: 'Mount inside of play' }, }); const canvas = await mount( // 👇 Pass data that is created inside of the play function to the component // For example, a just-generated UUID , ); await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i })); }, argTypes: { // 👇 Make the params prop un-controllable, as the value is always overridden in the play function. params: { control: { disable: true } }, }, }; ``` ```tsx // Page.stories.tsx — CSF Next 🧪 // 👇 Automocked module resolves to '../lib/__mocks__/db' const meta = preview.meta({ component: Page }); export const Basic = meta.story({ play: async ({ mount, args, userEvent }) => { const note = await db.note.create({ data: { title: 'Mount inside of play' }, }); const canvas = await mount( // 👇 Pass data that is created inside of the play function to the component // For example, a just-generated UUID , ); await userEvent.click(await canvas.findByRole('menuitem', { name: /login to add/i })); }, argTypes: { // 👇 Make the params prop un-controllable, as the value is always overridden in the play function. params: { control: { disable: true } }, }, }); ``` When you call `mount()` with no arguments, the component is rendered using the story’s render function, whether the [implicit default](https://storybook.js.org/docs/api/csf/index.md#default-render-functions) or the [explicit custom definition](https://storybook.js.org/docs/api/csf/index.md#custom-render-functions). When you mount a specific component inside the `mount` function like in the example above, the story’s render function will be ignored. This is why you must forward the `args` to the component. ### Run code before each story in a file Sometimes you might need to run the same code before each story in a file. For instance, you might need to set up the initial state of the component or modules. You can do this by adding an asynchronous `beforeEach` function to the component meta. You can return a cleanup function from the `beforeEach` function, which will run **after** each story, when the story is remounted or navigated away from. Generally, you should reset component and module state in the [preview file's `beforeAll` or `beforeEach` functions](#set-up-or-reset-state-for-all-tests), to ensure it applies to your entire project. However, if a component's needs are particularly unique, you can use the returned cleanup function in the component meta `beforeEach` to reset the state as needed. ```ts // Page.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Page, // 👇 Set the value of Date for every story in the file async beforeEach() { MockDate.set('2024-02-14'); // 👇 Reset the Date after each story return () => { MockDate.reset(); }; }, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { async play({ canvas }) { // ... This will run with the mocked Date }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Page, // 👇 Set the value of Date for every story in the file async beforeEach() { MockDate.set('2024-02-14'); // 👇 Reset the Date after each story return () => { MockDate.reset(); }; }, }); export const Basic = meta.story({ async play({ canvas }) { // ... This will run with the mocked Date }, }); ``` ### Set up or reset state for all tests When you [alter a component's state](#run-code-before-the-component-gets-rendered), it's important to reset that state before rendering another story to maintain isolation between tests. There are two options for resetting state, `beforeAll` and `beforeEach`. #### `beforeAll` The `beforeAll` function in the preview file (`.storybook/preview.*`) will run once before any stories in the project and will _not_ re-run between stories. Beyond its initial run when kicking off a test run, it will not run again unless the preview file is updated. This is a good place to bootstrap your project or run any setup that your entire project depends on, as in the example below. You can return a cleanup function from the `beforeAll` function, which will run before re-running the `beforeAll` function or during the teardown process in the test runner. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { async beforeAll() { await init(); }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ async beforeAll() { await init(); }, }); ``` #### `beforeEach` Unlike `beforeAll`, which runs only once, the `beforeEach` function in the preview file (`.storybook/preview.*`) will run before each story in the project. This is best used for resetting state or modules that are used by all or most of your stories. In the example below, we use it to reset the mocked Date. You can return a cleanup function from the `beforeEach` function, which will run **after** each story, when the story is remounted or navigated away from. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { async beforeEach() { MockDate.reset(); }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ async beforeEach() { MockDate.reset(); }, }); ``` It is _not_ necessary to restore `fn()` mocks, as Storybook will already do that automatically before rendering a story. See the [`parameters.test.restoreMocks` API](https://storybook.js.org/docs/api/parameters.md#restoremocks) for more information. ### Make assertions after interactions Sometimes, you may need to make assertions or run code after the component has been rendered and interacted with. #### `afterEach` `afterEach` runs after the story is rendered and the play function has completed. It can be used at the project level in the preview file (`.storybook/preview.*`), at the component level in the component meta, or at the story level in the story definition. This is useful for making assertions after the component has been rendered and interacted with, such as running checks on the final rendered output or logging information. Like the `play` function, `afterEach` receives the `context` object, which contains the `args`, `canvas`, and other properties related to the story. You can use this to make assertions or run code after the story has been rendered. ```ts // Page.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Page, // 👇 Runs after each story in this file async afterEach(context) { console.log(`✅ Tested ${context.name} story`); }, } satisfies Meta; export default meta; type Story = StoryObj; export const Basic: Story = { async play({ canvas }) { // ... }, }; ``` ```ts // Page.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Page, // 👇 Runs after each story in this file async afterEach(context) { console.log(`✅ Tested ${context.name} story`); }, }); export const Basic = meta.story({ async play({ canvas }) { // ... }, }); ``` You should not use `afterEach` to reset state in your tests. Because it runs after the story, resetting state here could prevent you from seeing the correct end state of your story. Instead, use the [`beforeEach`'s returned cleanup function](#beforeeach) to reset state, which will run only when navigating between stories to preserve the end state. ### Group interactions with the step function For complex flows, it can be worthwhile to group sets of related interactions together using the step function. This allows you to provide a custom label that describes a set of interactions: ```ts // MyComponent.stories.ts — CSF 3 // ...rest of story file export const Submitted: Story = { play: async ({ args, canvas, step, userEvent }) => { await step('Enter email and password', async () => { await userEvent.type(canvas.getByTestId('email'), 'hi@example.com'); await userEvent.type(canvas.getByTestId('password'), 'supersecret'); }); await step('Submit form', async () => { await userEvent.click(canvas.getByRole('button')); }); }, }; ``` ```ts // MyComponent.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: MyComponent, }); export const Submitted = meta.story({ play: async ({ args, canvas, step }) => { await step('Enter email and password', async () => { await userEvent.type(canvas.getByTestId('email'), 'hi@example.com'); await userEvent.type(canvas.getByTestId('password'), 'supersecret'); }); await step('Submit form', async () => { await userEvent.click(canvas.getByRole('button')); }); }, }); ``` This will show your interactions nested in a collapsible group: ![Interaction testing with labeled steps](../_assets/writing-tests/storybook-addon-interactions-steps.png) ### Mocked modules If your component depends on modules that are imported into the component file, you can mock those modules to control and assert on their behavior. This is detailed in the [mocking modules guide](https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules.md). You can then import the mocked module (which has all of the helpful methods of a [Vitest mocked function](https://vitest.dev/api/mock.html)) into your story and use it to assert on the behavior of your component: ```ts // NoteUI.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. // 👇 Automocked module resolves to '../app/__mocks__/actions' const meta = { component: NoteUI } satisfies Meta; export default meta; type Story = StoryObj; const notes = createNotes(); export const SaveFlow: Story = { name: 'Save Flow ▶', args: { isEditing: true, note: notes[0], }, play: async ({ canvas, userEvent }) => { const saveButton = canvas.getByRole('menuitem', { name: /done/i }); await userEvent.click(saveButton); // 👇 This is the mock function, so you can assert its behavior await expect(saveNote).toHaveBeenCalled(); }, }; ``` ```ts // NoteUI.stories.ts — CSF Next 🧪 // 👇 Automocked module resolves to '../app/__mocks__/actions' const meta = preview.meta({ component: NoteUI }); const notes = createNotes(); export const SaveFlow = meta.story({ name: 'Save Flow ▶', args: { isEditing: true, note: notes[0], }, play: async ({ canvas, userEvent }) => { const saveButton = canvas.getByRole('menuitem', { name: /done/i }); await userEvent.click(saveButton); // 👇 This is the mock function, so you can assert its behavior await expect(saveNote).toHaveBeenCalled(); }, }); ``` ## Running interaction tests If you're using the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md), you can run your interaction tests in these ways: - [In the Storybook UI](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#storybook-ui) - [In your editor](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#editor-extension) - [Via the CLI](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#cli) - [In CI environments](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#in-ci) In the Storybook UI, you can run interaction tests by clicking the **Run component tests** button in the expanded testing widget in the sidebar or by opening the context menu (three dots) on a story or folder and selecting **Run component tests**. ![Test widget, expanded, hovering run component tests button](../_assets/writing-tests/test-widget-run-component-tests.png) If you're using the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md), you can run your interaction tests in the terminal or in CI environments. ## Debugging interaction tests If you check the Interactions panel, you'll see the step-by-step flow defined in your play function for each story. It also offers a handy set of UI controls to pause, resume, rewind, and step through each interaction. Any test failures will also show up here, making it easy to quickly pinpoint the exact point of failure. In this example, the logic is missing to set the `submitted` state after pressing the Log in button. ### Permalinks for reproductions Because Storybook is a webapp, anyone with the URL can reproduce the failure with the same detailed information without any additional environment configuration or tooling required. ![Interaction testing with a failure](../_assets/writing-tests/interactions-failure.png) Streamline interaction testing further by automatically [publishing Storybook](https://storybook.js.org/docs/sharing/publish-storybook.md) in pull requests. That gives teams a universal reference point to test and debug stories. ## Automate with CI When you run your tests with the Vitest addon, automating those tests is as simple as running your tests in your CI environment. Please see the [testing in CI guide](https://storybook.js.org/docs/writing-tests/in-ci.md) for more information. If you cannot use the Vitest addon, you can still run your tests in CI using the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md). ## Troubleshooting ### What’s the difference between interaction tests and visual tests? Interaction tests can be expensive to maintain when applied wholesale to every component. We recommend combining them with other methods like visual testing for comprehensive coverage with less maintenance work. ### What's the difference between interaction tests and using Vitest + Testing Library alone? Interaction tests integrate Vitest and Testing Library into Storybook. The biggest benefit is the ability to view the component you're testing in a real browser. That helps you debug visually, instead of getting a dump of the (fake) DOM in the command line or hitting the limitations of how JSDOM mocks browser functionality. It's also more convenient to keep stories and tests together in one file than having them spread across files. **More testing resources** - [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) for running tests in Storybook - [Accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) for accessibility - [Visual testing](https://storybook.js.org/docs/writing-tests/visual-testing.md) for appearance - [Snapshot testing](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) for rendering errors and warnings - [Test coverage](https://storybook.js.org/docs/writing-tests/test-coverage.md) for measuring code coverage - [CI](https://storybook.js.org/docs/writing-tests/in-ci.md) for running tests in your CI/CD pipeline - [End-to-end testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-end-to-end-tests.md) for simulating real user scenarios - [Unit testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md) for functionality - [Test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md) to automate test execution > Source: https://storybook.js.org/docs/writing-tests/interaction-testing --- # Accessibility tests Web accessibility is the practice of making websites and apps accessible and inclusive to all people, regardless of ability or the technology they’re using. That means supporting requirements such as keyboard navigation, screen reader support, sufficient color contrast, etc. Accessibility is not only the right thing to do, but it is increasingly mandated. For example, the [European Accessibility Act](https://ec.europa.eu/social/main.jsp?catId=1202) imposes specific accessibility requirements. Similarly in the US, laws like the [Americans with Disabilities Act (ADA)](https://www.ada.gov/) and [Section 508 of the Rehabilitation Act](https://www.section508.gov/) apply to many public-facing services. Many of these laws are based on [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/), a standardized guideline for making web content accessible. Accessibility tests audit the rendered DOM against a set of heuristics based on WCAG rules and other industry-accepted best practices. They act as the first line of QA to catch blatant accessibility violations. ## Install the addon Storybook provides an Accessibility (a11y) addon to help ensure the accessibility of your components. It is built on top of Deque’s [axe-core library](https://github.com/dequelabs/axe-core), which automatically catches [up to 57% of WCAG issues](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/). Run this command to install and configure the addon in your project: ```shell npx storybook add @storybook/addon-a11y ``` ```shell pnpm exec storybook add @storybook/addon-a11y ``` ```shell yarn exec storybook add @storybook/addon-a11y ``` Storybook's [`add`](https://storybook.js.org/docs/api/cli-options.md#add) command automates the addon's installation and setup. To install it manually, see our [documentation](https://storybook.js.org/docs/addons/install-addons.md#manual-installation) on how to install addons. Your Storybook will now include some features to check the accessibility of your components, including a button in the toolbar to simulate different vision impairments and an Accessibility addon panel to check for violations. ![Storybook UI with accessibility features annotated](../_assets/writing-tests/addon-a11y-annotated.png) ### Integration with Vitest addon The accessibility addon is designed to integrate with the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md), so that you can [run accessibility tests](#run-accessibility-tests) alongside your component tests. To get started, run this command, which will install and configure the Vitest addon and Vitest: ```shell npx storybook add @storybook/addon-vitest ``` ```shell pnpm exec storybook add @storybook/addon-vitest ``` ```shell yarn exec storybook add @storybook/addon-vitest ``` The full installation instructions, including project requirements, are available in the [Vitest addon documentation](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#install-and-set-up). ## Check for violations When you navigate to a story, automated accessibility checks are run and the results are reported in the Accessibility addon panel. The results are broken down into three sub-tabs: - **Violations** are known violations of WCAG rules and best practices - **Passes** are known non-violations - **Incomplete** highlights areas that you should confirm manually because they could not be checked automatically ## Configure Because the addon is built on top of `axe-core`, much of the configuration available maps to its available options: | Property | Default | Description | | ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `parameters.a11y.context` | `'body'` | [Context](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter) passed to `axe.run`. Defines which elements to run checks against. | | `parameters.a11y.config` | (see below) | Configuration passed to [`axe.configure()`](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). Most commonly used to [configure individual rules](#individual-rules). | | `parameters.a11y.options` | `{}` | [Options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) passed to `axe.run`. Can be used to adjust the rulesets checked against. | | `parameters.a11y.test` | `undefined` | Determines test behavior when run with the Vitest addon. [More details below](#test-behavior). | | `globals.a11y.manual` | `undefined` | Set to `true` to prevent stories from being automatically analyzed when visited. [More details below](#disable-automated-checks) |
Default `parameters.a11y.config` By default, Storybook disables the [region rule](https://dequeuniversity.com/rules/axe/4.10/region?application=RuleDescription), which does not typically apply to components in stories and can lead to false negatives. ```js { rules: [ { id: 'region', enabled: false, }, ], } ```
We’ll share examples to show how to use some of these configuration properties. Here, they are applied to all stories in a project, in `.storybook/preview.*`: ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. Typically, this is the CSS selector for the part of the DOM you want to analyze. */ context: 'body', /* * Axe's configuration * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure * to learn more about the available properties. */ config: {}, /* * Axe's options parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter * to learn more about the available options. */ options: {}, }, }, initialGlobals: { a11y: { // Optional flag to prevent the automatic check manual: true, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ addons: [addonA11y()], parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. Typically, this is the CSS selector for the part of the DOM you want to analyze. */ context: 'body', /* * Axe's configuration * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure * to learn more about the available properties. */ config: {}, /* * Axe's options parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter * to learn more about the available options. */ options: {}, }, }, initialGlobals: { a11y: { // Optional flag to prevent the automatic check manual: true, }, }, }); ``` You can also apply the configuration for all stories in a file (in the `meta`) or an individual story: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. */ context: {}, /* * Axe's configuration * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure * to learn more about the available properties. */ config: {}, /* * Axe's options parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter * to learn more about the available options. */ options: {}, /* * Configure test behavior * See: https://storybook.js.org/docs/next/writing-tests/accessibility-testing#test-behavior */ test: 'error', }, }, globals: { a11y: { // Optional flag to prevent the automatic check manual: true, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const ExampleStory: Story = { parameters: { a11y: { // ...same config available as above }, }, globals: { a11y: { // ...same config available as above }, }, }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. */ context: {}, /* * Axe's configuration * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure * to learn more about the available properties. */ config: {}, /* * Axe's options parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter * to learn more about the available options. */ options: {}, /* * Configure test behavior * See: https://storybook.js.org/docs/next/writing-tests/accessibility-testing#test-behavior */ test: 'error', }, }, globals: { a11y: { // Optional flag to prevent the automatic check manual: true, }, }, }); export const ExampleStory = meta.story({ parameters: { a11y: { // ...same config available as above }, }, globals: { a11y: { // ...same config available as above }, }, }); ``` ### Rulesets The addon uses the `axe-core` library to run accessibility checks. By default, it runs a set of rules that are based on the WCAG 2.0 and 2.1 guidelines, as well as some best practices: - [WCAG 2.0 Level A & AA Rules](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md#wcag-20-level-a--aa-rules) - [WCAG 2.1 Level A & AA Rules](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md#wcag-21-level-a--aa-rules) - [Best Practices Rules](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md#best-practices-rules) You can find a breakdown of these rulesets, as well as the other rulesets available in [axe-core’s documentation](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md#wcag-2x-level-aaa-rules). To change the rules that are checked against (e.g. to check against WCAG 2.2 AA or WCAG 2.x AAA rules), use the [`runOnly` option](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter-examples): ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { a11y: { options: { /* * Opt in to running WCAG 2.x AAA rules * Note that you must explicitly re-specify the defaults (all but the last array entry) * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter-examples for more details */ runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', 'wcag2aaa'], }, }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ addons: [addonA11y()], parameters: { a11y: { options: { /* * Opt in to running WCAG 2.x AAA rules * Note that you must explicitly re-specify the defaults (all but the last array entry) * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter-examples for more details */ runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', 'wcag2aaa'], }, }, }, }); ``` ### Individual rules You can also enable, disable, or configure individual rules. This can be done in the `config` property of the `parameters.a11y` object. For example: ```ts // Button.stories.ts — CSF 3 // ...rest of story file export const IndividualA11yRulesExample: Story = { parameters: { a11y: { config: { rules: [ { // The autocomplete rule will not run based on the CSS selector provided id: 'autocomplete-valid', selector: '*:not([autocomplete="nope"])', }, { // Setting the enabled option to false will disable checks for this particular rule on all stories. id: 'image-alt', enabled: false, }, ], }, }, }, }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const IndividualA11yRulesExample = meta.story({ parameters: { a11y: { config: { rules: [ { // The autocomplete rule will not run based on the CSS selector provided id: 'autocomplete-valid', selector: '*:not([autocomplete="nope"])', }, { // Setting the enabled option to false will disable checks for this particular rule on all stories. id: 'image-alt', enabled: false, }, ], }, }, }, }); ``` ### Test behavior You can configure accessibility tests with the `parameters.a11y.test` [parameter](https://storybook.js.org/docs/writing-stories/parameters.md), which determines the behavior of accessibility tests for a story when run with either the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) or the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md). The parameter accepts three values: | Value | Description | | --------- | ---------------------------------------------------------------------------------------- | | `'off'` | Do not run accessibility tests (you can still manually verify via the addon panel) | | `'todo'` | Run accessibility tests; violations return a warning in the Storybook UI | | `'error'` | Run accessibility tests; violations return a failing test in the Storybook UI and CLI/CI | Like other parameters, you can define it at the project level in `.storybook/preview.*`, the component level in the meta (or default export) of the story file, or the individual story level. For example, to fail on accessibility tests for all stories in a file except one: ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, parameters: { // 👇 Applies to all stories in this file a11y: { test: 'error' }, }, } satisfies Meta; export default meta; type Story = StoryObj; // 👇 This story will use the 'error' value and fail on accessibility violations export const Primary: Story = { args: { primary: true }, }; // 👇 This story will not fail on accessibility violations // (but will still run the tests and show warnings) export const NoA11yFail: Story = { parameters: { a11y: { test: 'todo' }, }, }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, parameters: { // 👇 Applies to all stories in this file a11y: { test: 'error' }, }, }); // 👇 This story will use the 'error' value and fail on accessibility violations export const Primary = meta.story({ args: { primary: true }, }); // 👇 This story will not fail on accessibility violations // (but will still run the tests and show warnings) export const NoA11yFail = meta.story({ parameters: { a11y: { test: 'todo' }, }, }); ``` Why is the value called "todo" instead of "warn"? This value is intended to serve as a literal `TODO` in your codebase. It can be used to mark stories that you know have accessibility issues but are not ready to fix yet. This way, you can keep track of them and address them later. The `'off'` value should only be used for stories that do not need to be tested for accessibility, such as one used to demonstrate an antipattern in a component's usage. You can also [disable individual rules](#individual-rules) when they are not applicable to your use case. ### Excluded elements Sometimes, it may be necessary to exclude certain elements from the accessibility checks. For this, you can define a custom [context](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter) to select which elements are included (or excluded) when running checks. For example, this story will ignore elements with the class `no-a11y-check`: ```ts // Button.stories.ts — CSF 3 // ...rest of story file export const ExampleStory: Story = { parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. */ context: { include: ['body'], exclude: ['.no-a11y-check'], }, }, }, }; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, }); export const ExampleStory = meta.story({ parameters: { a11y: { /* * Axe's context parameter * See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter * to learn more. */ context: { include: ['body'], exclude: ['.no-a11y-check'], }, }, }, }); ``` ### Disable automated checks When you disable automated accessibility checks, the addon will not run any tests when you navigate to a story or when you [run the tests with the Vitest addon](#run-accessibility-tests). You can still manually trigger checks in the Accessibility addon panel. This is useful for stories that are not meant to be accessible, such as those demonstrating an antipattern or a specific use case. Disable automated accessibility checks for stories or components by adding the following globals to your stories or meta (or default export): ```ts // MyComponent.stories.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const meta = { component: MyComponent, } satisfies Meta; export default meta; type Story = StoryObj; export const NonA11yStory: Story = { globals: { a11y: { // This option disables all automatic a11y checks on this story manual: true, }, }, }; ``` ```ts // MyComponent.stories.ts|tsx — CSF Next 🧪 const meta = preview.meta({ component: MyComponent, }); export const NonA11yStory = meta.story({ globals: { a11y: { // This option disables all automatic a11y checks on this story manual: true, }, }, }); ``` ## Run accessibility tests ### With the Vitest addon If you're using the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md), you can run your accessibility tests, as part of component tests, in these ways: - [In the Storybook UI](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#storybook-ui) - [In CI environments](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon/index.md#in-ci) To run accessibility tests in the Storybook UI, first expand the testing widget in the sidebar and check the Accessibility checkbox. Now, when you press the Run component tests button, the accessibility tests will be run along with any other tests you have configured. ![Test widget, expanded, with accessibility checked](../_assets/writing-tests/test-widget-a11y-enabled.png) After running the tests, you will see the results in the sidebar, which will add a test status indicator next to each story that was tested. You can press on these indicators to open a menu with the Accessibility test result. Pressing on that result will navigate to that story and open the Accessibility panel, where you view details about each violation and suggestions toward how to fix them. ![Storybook showing a failing accessibility test in both the sidebar story menu and the Accessibility panel](../_assets/writing-tests/test-a11y-overview.png) If any of your tests have warnings or failures, the testing widget will show the number of warnings and failures. You can press on these to filter the stories in the sidebar to only show those with warnings or failures. In CI, accessibility tests are run automatically for stories with [`parameters.a11y.test = 'error'`](#test-behavior) when you run the Vitest tests. ### With the test-runner If you're using the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md), you can run your accessibility tests in the terminal or in CI environments. Accessibility tests are included in your test run when you have the Accessibility addon installed and [`parameters.a11y.test`](#test-behavior) is set to a value other than `'off'`. ## Debug accessibility violations When you run accessibility tests, the results are reported in the Storybook UI. You can click on a violation to see more details about it, including the rule that was violated and suggestions for how to fix it. You can also toggle on highlighting in the Storybook UI to see which elements are causing the violation, and click on a highlighted element to see the violations details in a popover menu. ![Storybook UI with a highlighted element with a popover menu showing accessbility violation details](../_assets/writing-tests/addon-a11y-debug-violations.png) ## Automate with CI When you run your accessibility tests with the Vitest addon, automating them is as simple as running them in your CI environment. For more information, please see the [testing in CI guide](https://storybook.js.org/docs/writing-tests/in-ci.md). Accessibility tests will only produce errors in CI if you have set [`parameters.a11y.test`](#test-behavior) to `'error'`. If you set it to `'todo'`, there will be no accessibility-related errors, warnings, or output in CI, but you can still see the results as warnings in the Storybook UI when you run the tests locally. If you cannot use the Vitest addon, you can still run your tests in CI using the [test-runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md). ## Recommended workflow You can use configuration to progressively work toward a more accessible UI by combining multiple test behaviors. For example, you can start with `'error'` to fail on accessibility violations, then switch to `'todo'` to mark components that need fixing, and finally remove the todos once all stories pass accessibility tests: 1. Update your project configuration to fail on accessibility violations by setting [`parameters.a11y.test`](#test-behavior) to `'error'`. This ensures that all new stories are tested to meet accessibility standards. ```ts // .storybook/preview.ts|tsx — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const preview: Preview = { parameters: { // 👇 Fail all accessibility tests when violations are found a11y: { test: 'error' }, }, }; export default preview; ``` ```ts // .storybook/preview.tsx — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default definePreview({ addons: [addonA11y()], parameters: { // 👇 Fail all accessibility tests when violations are found a11y: { test: 'error' }, }, }); ``` 2. You will likely find that many components have accessibility failures (and maybe feel a bit overwhelmed!). 3. Take note of the components with accessibility issues and temporarily reduce their failures to warnings by applying the `'todo'` parameter value. This keeps accessibility issues visible while not blocking development. This is also a good time to commit your work as a baseline for future improvements. ```ts // DataTable.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: DataTable, parameters: { // 👇 This component's accessibility tests will not fail // Instead, they display warnings in the Storybook UI a11y: { test: 'todo' }, }, } satisfies Meta; export default meta; ``` ```ts // DataTable.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: DataTable, parameters: { // 👇 This component's accessibility tests will not fail // Instead, they display warnings in the Storybook UI a11y: { test: 'todo' }, }, }); ``` 4. Pick a good starting point from the components you just marked `'todo'` (we recommend starting with something like Button, as it's a fundamental component used within many others). Fix the issues in that component using the suggestions in the addon panel to ensure it passes accessibility tests, then remove the parameter. ```ts // Button.stories.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const meta = { component: Button, parameters: { // 👇 Remove this once all stories pass accessibility tests // a11y: { test: 'todo' }, }, } satisfies Meta; export default meta; ``` ```ts // Button.stories.ts — CSF Next 🧪 const meta = preview.meta({ component: Button, parameters: { // 👇 Remove this once all stories pass accessibility tests // a11y: { test: 'todo' }, }, }); ``` 5. Pick another component and repeat the process until you've covered all your components and you're an accessibility hero! ## FAQ ### What’s the difference between browser-based and linter-based accessibility tests? Browser-based accessibility tests, like those found in Storybook, evaluate the rendered DOM because that gives you the highest accuracy. Auditing code that hasn't been compiled yet is one step removed from the real thing, so you won't catch everything the user might experience. ### Why are my tests failing in different environments? With the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md), your tests run in Vitest using your project's configuration with Playwright's Chromium browser. This can lead to inconsistent test results reported in the Storybook UI or CLI. The inconsistency can be due to `axe-core` reporting different results in different environments, such as browser versions or configurations. If you encounter this issue, we recommend reaching out using the default communication channels (e.g., [GitHub discussions](https://github.com/storybookjs/storybook/discussions/new?category=help), [Github issues](https://github.com/storybookjs/storybook/issues/new?template=bug_report.yml)). ### The addon panel does not show expected violations Modern React components often use asynchronous techniques like [Suspense](https://react.dev/reference/react/Suspense) or [React Server Components (RSC)](https://react.dev/reference/rsc/server-components) to handle complex data fetching and rendering. These components don’t immediately render their final UI state. Storybook doesn’t inherently know when an async component has fully rendered. As a result, the a11y checks sometimes run too early, before the component finishes rendering, leading to false negatives (no reported violations even if they exist). To address this issue, we have introduced a feature flag: `developmentModeForBuild`. This feature flag allows you to set `process.env.NODE_ENV` to `'development'` in built Storybooks, enabling development-related optimizations that are typically disabled in production builds. One of those development optimizations is React’s [`act` utility](https://react.dev/reference/react/act), which helps ensure that all updates related to a test are processed and applied before making assertions, like a11y checks. To enable this feature flag, add the following configuration to your `.storybook/main.js|ts` file: ```ts // .storybook/main.ts — CSF 3 // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], features: { developmentModeForBuild: true, }, }; export default config; ``` ```ts // .storybook/main.ts — CSF Next 🧪 // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], features: { developmentModeForBuild: true, }, }); ``` **More testing resources** - [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) for running tests in Storybook - [Interaction testing](https://storybook.js.org/docs/writing-tests/interaction-testing.md) for user behavior simulation - [Visual testing](https://storybook.js.org/docs/writing-tests/visual-testing.md) for appearance - [Snapshot testing](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) for rendering errors and warnings - [Test coverage](https://storybook.js.org/docs/writing-tests/test-coverage.md) for measuring code coverage - [CI](https://storybook.js.org/docs/writing-tests/in-ci.md) for running tests in your CI/CD pipeline - [End-to-end testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-end-to-end-tests.md) for simulating real user scenarios - [Unit testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md) for functionality - [Test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md) to automate test execution > Source: https://storybook.js.org/docs/writing-tests/accessibility-testing --- # Visual tests Visual tests are the most efficient way to test your components. With the click of a button you can take snapshots of every story in your Storybook and compare those snapshots to baselines — last known “good” snapshots. Not only does this allow you to check the appearance of your components, but they are also able to check a large subset of component functionality [without having to write or maintain any test code](https://storybook.js.org/blog/visual-testing-is-the-greatest-trick-in-ui-development/)! Storybook supports cross-browser visual testing natively using [Chromatic](https://www.chromatic.com/storybook/?ref=storybook_site), a cloud service made by the Storybook team. When you enable visual testing, every story is automatically turned into a test. This gives you instant feedback on UI bugs directly in Storybook. ## Install the addon Add visual tests to your project by installing `@chromatic-com/storybook`, the official addon by Storybook maintainers: ```shell npx storybook@latest add @chromatic-com/storybook ``` ```shell pnpm dlx storybook@latest add @chromatic-com/storybook ``` ```shell yarn dlx storybook@latest add @chromatic-com/storybook ``` ## Enable visual tests When you start Storybook, you'll see a new addon panel for Visual Tests where you can run tests and view results. ![Visual Tests addon enabled](../_assets/writing-tests/vta-enable.png) Already using the [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md)? In the expanded testing widget, you’ll now see a Visual tests section: ![Expanded testing widget, showing the Visual tests section](../_assets/writing-tests/test-widget-expanded-with-vta.png) Clicking the Run tests button at the bottom will run _all_ tests, both component and visual. First, sign in to your Chromatic account. If you do not have an account, you can create one as part of the sign in process. Once signed in, you will see your Chromatic account(s) and their projects. Either select one from the list or create a new one. ![Visual Tests addon project selection](../_assets/writing-tests/vta-select-project.png) Now that you have linked your project to the addon, you can press the “Catch a UI change” button to run your first build of visual tests. ![Visual test panel showing the Catch a UI change button](../_assets/writing-tests/vta-project-linked.png) That first build will create the baseline snapshots for your stories, which will be compared against when you run visual tests again. ## Run visual tests After you have made a code change, there are two ways to run visual tests in Storybook. In the expanded testing widget in the sidebar, press the run button in the Visual tests section. ![Test widget showing the Run visual tests button](../_assets/writing-tests/test-widget-expanded-with-vta.png) Or, in the Visual tests addon panel, press the run button in the top right corner of the panel. ![Visual tests addon panel showing the Run visual tests button](../_assets/writing-tests/vta-run-from-panel.png) Either method will send your stories to the cloud to take snapshots and detect visual changes. ## Review changes If there are visual changes in your stories, they will be 🟡 highlighted in the sidebar. Click the story and go to the Visual Tests addon panel to see which pixels changed. If the changes are intentional, ✅ accept them as baselines locally. If the changes aren't intentional, fix the story and rerun the tests using the rerun button. ![Visual test panel with diff](../_assets/writing-tests/vta-changes-found.png) When you finish accepting changes as baselines in the addon, you're ready to push the code to your remote repository. This will sync baselines to the cloud for anyone who checks out your branch. ![Visual test panel with accepted baselines](../_assets/writing-tests/vta-changes-accepted.png) ## Automate with CI The addon is designed to be used in tandem with CI. We recommend using the addon to check for changes during development and then running visual tests in CI as you get ready to merge. Changes you accept as baselines in the addon will get auto-accepted as baselines in CI so you don’t have to review twice. 1. Add a step to your CI workflow to run Chromatic. - [GitHub Actions](https://chromatic.com/docs/github-actions?ref=storybook_docs) - [GitLab Pipelines](https://chromatic.com/docs/gitlab?ref=storybook_docs) - [Bitbucket Pipelines](https://chromatic.com/docs/bitbucket-pipelines?ref=storybook_docs) - [CircleCI](https://chromatic.com/docs/circleci?ref=storybook_docs) - [Travis CI](https://chromatic.com/docs/travisci?ref=storybook_docs) - [Jenkins](https://chromatic.com/docs/jenkins?ref=storybook_docs) - [Azure Pipelines](https://chromatic.com/docs/azure-pipelines?ref=storybook_docs) - [Custom CI provider](https://chromatic.com/docs/custom-ci-provider?ref=storybook_docs) 2. Configure your CI to include environment variables to authenticate with Chromatic (project token). ## PR checks Once you successfully set up Chromatic in CI, your pull/merge requests will be badged with a UI Tests check. The badge notifies you of test errors or UI changes that need to be verified by your team. Make the check required in your Git provider to prevent accidental UI bugs from being merged. ![](../_assets/writing-tests/vta-prbadge-test.png) ## Configure The addon includes configuration options covering most use cases by default. You can also fine-tune the addon configuration to match your project's requirements via the [`./chromatic.config.json`](https://www.chromatic.com/docs/visual-tests-addon/#configure) file. Below is a shortlist of addon-specific options and examples of how to use them. View the full list of [options](https://www.chromatic.com/docs/configure/#options). | Option | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `projectId` | Automatically configured. Sets the value for the project identifier
`"projectId": "Project:64cbcde96f99841e8b007d75"` | | `buildScriptName` | Optional. Defines the custom Storybook build script
`"buildScriptName": "deploy-storybook"` | | `debug` | Optional. Output verbose debugging information to the console.
`"debug": true` | | `zip` | Optional. Recommended for large projects. Configures the addon to deploy your Storybook to Chromatic as a zip file.
`"zip": true` | ```json title="./chromatic.config.json" { "buildScriptName": "deploy-storybook", "debug": true, "projectId": "Project:64cbcde96f99841e8b007d75", "zip": true } ``` ## FAQs ### What’s the difference between visual tests and snapshot tests? [Snapshot tests](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) compare the rendered markup of every story against known baselines. This means the test compares blobs of HTML and not what the user actually sees. Which in turn, can lead to an increase in false positives as code changes don’t always yield visual changes in the component. Visual tests compare the rendered pixels of every story against known baselines. Because you're testing the same thing your users actually experience, your tests will be richer and easier to maintain. **More testing resources** - [Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon.md) for running tests in Storybook - [Interaction testing](https://storybook.js.org/docs/writing-tests/interaction-testing.md) for user behavior simulation - [Accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing.md) for accessibility - [Snapshot testing](https://storybook.js.org/docs/writing-tests/snapshot-testing.md) for rendering errors and warnings - [Test coverage](https://storybook.js.org/docs/writing-tests/test-coverage.md) for measuring code coverage - [CI](https://storybook.js.org/docs/writing-tests/in-ci.md) for running tests in your CI/CD pipeline - [End-to-end testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-end-to-end-tests.md) for simulating real user scenarios - [Unit testing](https://storybook.js.org/docs/writing-tests/integrations/stories-in-unit-tests.md) for functionality - [Test runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner.md) to automate test execution > Source: https://storybook.js.org/docs/writing-tests/visual-testing --- # Snapshot tests ## Snapshot tests Snapshot testing involves rendering a component in a given state, taking a snapshot of the rendered DOM or HTML, and then comparing it against the previous snapshot. They’re convenient to create, but can be difficult and noisy to maintain if the snapshot contains too much information. For UI components, [visual tests](https://storybook.js.org/docs/writing-tests/visual-testing.md) (easier to review) or [interaction tests](https://storybook.js.org/docs/writing-tests/interaction-testing.md) (focused on functionality) are usually the better fit. However, there are some cases where snapshot testing may be necessary, such as ensuring an error is thrown correctly. You can reuse your stories as the basis of snapshot tests within another test environment, like Jest or Vitest. To enable this, Storybook provides the Portable Stories API, which composes your stories with their annotations ([args](https://storybook.js.org/docs/writing-stories/args.md), [decorators](https://storybook.js.org/docs/writing-stories/decorators.md), [parameters](https://storybook.js.org/docs/writing-stories/parameters.md), etc) and produces a renderable element for your tests. Portable Stories are available for: - [Vitest](https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest.md) - [Jest](https://storybook.js.org/docs/api/portable-stories/portable-stories-jest.md) - [Playwright CT](https://storybook.js.org/docs/api/portable-stories/portable-stories-playwright.md) Looking for snapshot testing with Storyshots? Storyshots is deprecated and no longer maintained. We recommend using the Portable Stories API instead. Please reference the [Storyshots documentation](https://storybook.js.org/docs/8/writing-tests/snapshot-testing/storyshots-migration-guide.md) for more information on how to migrate your tests. ## Get started with Portable Stories If you’re using Storybook Test, your project is already configured to use Portable Stories in Vitest. If you’re not using Storybook Test or would like to test in another testing environment, please follow the relevant documentation: - [Vitest](https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest.md#1-apply-project-level-annotations) - [Jest](https://storybook.js.org/docs/api/portable-stories/portable-stories-jest.md#1-apply-project-level-annotations) - [Playwright CT](https://storybook.js.org/docs/api/portable-stories/portable-stories-playwright.md#1-apply-project-level-annotations) ## Snapshot testing a portable story Snapshot testing a reusable story is a straightforward process of using `composeStories` from the Portable Stories API to get a renderable element, rendering that element, and then taking and comparing a snapshot. This example renders a Button component in Vitest (by reusing one of Button’s stories) and asserts that the rendered HTML snapshot matches. ```js // test/Button.test.js|ts — jest // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const { Primary } = composeStories(stories); test('Button snapshot', async () => { await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` ```js // test/Button.test.js|ts — vitest // @vitest-environment jsdom // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. const { Primary } = composeStories(stories); test('Button snapshot', async () => { await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` Once the test has run, a snapshot will be inserted or created. Then, when you run tests again and the snapshot doesn’t match, the test will fail and you will see output something like this: ```diff FAIL src/components/ui/Button.test.ts > Button snapshot Error: Snapshot `Button snapshot 1` mismatched - Expected + Received
``` How long did it take you to find what changed? (`px-4` → `px-3`) This is exactly why [visual tests](https://storybook.js.org/docs/writing-tests/visual-testing.md) are so much better for testing the appearance of UI components. Not only is it immediately apparent what has changed, but it also tests the actual appearance your users will see, not merely the CSS applied. ### Verifying an error is thrown Now that we know how to do general snapshot testing, let’s apply it to a common use case: verifying that an expected error is thrown correctly. In this example, we have a simple Button React component which for some reason accepts a prop, `doNotUseThisItWillThrowAnError`, which will (unsurprisingly) throw an error if it is used. ```tsx title="Button.tsx" function Button(props) { if (props.doNotUseThisItWillThrowAnError) { throw new Error('I tried to tell you...'); } return