experimentalReview feature React only
The experimentalReview feature is experimental and React-specific. Type: boolean. Default: false. It enables the experimental agentic review feature, which allows you to review the work an AI agent has done in your Storybook. This feature builds on changeDetection, which must also be enabled (it is enabled by default).
highlight feature
The highlight feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the Highlight feature.
interactions feature
The interactions feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the Interactions feature for debugging interaction tests.
legacyDecoratorFileOrder feature
The legacyDecoratorFileOrder feature can be configured in main.js features. Type: boolean. It applies decorators from preview.js before decorators from addons or frameworks.
measure feature
The measure feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the Measure feature.
outline feature
The outline feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the Outline feature.
sidebarOnboardingChecklist feature
The sidebarOnboardingChecklist feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the onboarding checklist sidebar widget.
menuOnboardingChecklist feature
The menuOnboardingChecklist feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables a link to the onboarding guide in the menu.
toolbars feature
The toolbars feature can be configured in main.js features. Type: boolean.
viewport feature
The viewport feature can be enabled or disabled in main.js features configuration. Type: boolean. Default: true. Enables the Viewport feature.
angularFilterNonInputControls feature Angular only
The angularFilterNonInputControls feature is Angular-specific. Type: boolean. It filters non-input controls in Angular.
logLevel main.js configuration field
The logLevel field in main.js|ts configuration controls Storybook's logs in the browser terminal. It accepts the values 'debug', 'error', 'info', 'trace', or 'warn'. The default value is 'info'. This field is useful for debugging.
managerHead alternative: manager-head.html
If you do not need to programmatically adjust the manager head, you can add scripts and styles to a manager-head.html file instead of using the managerHead configuration function.
managerHead configuration key type and purpose
The managerHead configuration key is a function with type signature (head: string) => string. It allows you to programmatically adjust the manager's HTML <head> element in your Storybook, for example to load a custom font or add a script. It is most often used by addon authors.
previewAnnotations example implementation
Example from @storybook/nextjs framework's preset.ts: const previewAnnotations: StorybookConfig['previewAnnotations'] = (entry = []) => [...entry, import.meta.resolve('@storybook/nextjs/preview')]; This shows how frameworks extend the preview annotations array.
previewAnnotations intended usage
previewAnnotations is mostly used by Storybook frameworks during preset configuration. Storybook users and addon authors should add scripts to preview.js instead of using previewAnnotations in main.js.
previewAnnotations main.js configuration field
previewAnnotations is a field in main.js|ts configuration that accepts either an array of strings or a function. The function takes two parameters: config (an array of strings) and options (Options object), and returns either an array of strings or a Promise resolving to an array of strings. This field is used to add additional scripts to run in the story preview.
previewBody use case
previewBody can be used to conditionally add scripts or styles to the preview body depending on the environment. This is particularly useful for addon authors who need to dynamically modify the preview body based on runtime conditions.
previewBody main config key
previewBody is a main configuration key with type (body: string) => string. It programmatically adjusts the preview <body> of your Storybook. It is most often used by addon authors. The function takes a string parameter and returns a modified string. If you don't need programmatic adjustment, you can add scripts and styles to preview-body.html instead.
previewHead use case
previewHead can be used to conditionally add scripts or styles to the preview head depending on the environment or other conditions.
previewHead alternative: preview-head.html
If you don't need to programmatically adjust the preview head, you can add scripts and styles to preview-head.html instead of using previewHead.
previewHead configuration key
previewHead is a main.js|ts configuration option that takes a function with type (head: string) => string. It programmatically adjusts the preview <head> of your Storybook. This is most often used by addon authors.
Using a function for refs configuration
The refs configuration supports a function-based approach that allows dynamically configuring refs based on runtime configuration.
Disabling refs for package dependencies
Some package dependencies automatically compose their Storybook in yours. You can disable this automatic composition behavior by setting the disable property to true for the specific package name in the refs configuration.
refs configuration purpose
The refs configuration field in main.js|ts is used to configure Storybook composition, which allows composing multiple Storybooks together.
refs configuration type definition
The refs field in main.js|ts accepts an object where keys are reference identifiers and values can be either: (1) an object with properties title (string, required), url (string, required), expanded (boolean, optional), and sourceUrl (string, optional); (2) a function that receives a config object with title, url, expanded, and sourceUrl properties and returns an object with title, url, expanded, and sourceUrl; or (3) an object with a disable boolean property to disable composition.
Non-CSF source file to CSF transpilation example
Example of a non-CSF source file that generates stories dynamically:
```ts
// Button.variants.js|ts
import { variantsFromComponent, createStoryFromVariant } from '../utils';
import { Button } from './Button';
export const generateStories = () => {
const variants = variantsFromComponent(Button);
return variants.map((variant) => createStoryFromVariant(variant));
};
```
This gets transpiled to a CSF file like:
```js
// virtual:Button.variants.js|ts
import { Button } from './Button';
export default {
component: Button,
};
export const Primary = {
args: {
primary: true,
},
};
```
Custom indexer use cases
Common use cases for custom indexers include: (1) Generating stories dynamically from fixture data or API endpoints; (2) Generating stories with an alternative API extending the CSF format; (3) Defining stories in non-JavaScript languages by converting template files to CSF (examples include `@storybook/addon-svelte-csf` for Svelte and `storybook-vue-addon` for Vue); (4) Processing arbitrary content like URL collections and rendering them as sidebar links.
URL collection indexer and Vite plugin example
A custom indexer to process a collection of URLs and render them as sidebar links. First, create a URL collection file (e.g., `src/MyLinks.url.js`) with URLs listed as named exports:
```js
export default {};
export const DesignTokens = 'https://www.designtokens.org/';
export const CobaltUI = 'https://cobalt-ui.pages.dev/';
export const MiseEnMode = 'https://mode.place/';
export const IndexerAPI = 'https://github.com/storybookjs/storybook/discussions/23176';
```
Then create a Vite plugin to transform URL files into CSF components:
```ts
import * as acorn from 'acorn';
import * as walk from 'acorn-walk';
import { defineConfig, type Plugin } from 'vite';
function StorybookUrlLinksPlugin(): Plugin {
return {
name: 'storybook-url-links',
async transform(code: string, id: string) {
if (id.endsWith('.url.js')) {
const ast = acorn.parse(code, {
ecmaVersion: 2020,
sourceType: 'module',
});
const namedExports: string[] = [];
let defaultExport = 'export default {};';
walk.simple(ast, {
ExportNamedDeclaration(node: acorn.ExportNamedDeclaration) {
if (node.declaration && node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach((declaration) => {
if ('name' in declaration.id) {
namedExports.push(declaration.id.name);
}
});
}
},
ExportDefaultDeclaration(node: acorn.ExportDefaultDeclaration) {
defaultExport = code.slice(node.start, node.end);
},
});
return {
code: `
import RedirectBack from '../../.storybook/components/RedirectBack.svelte';
${namedExports
.map((name) => `export const ${name} = () => new RedirectBack();`)
.join('\n')}
${defaultExport}
`,
map: null,
};
}
},
};
}
```
URL indexer configuration example
Update your Storybook configuration to include the custom URL indexer:
```ts
import type { StorybookConfig } from '@storybook/your-framework';
import type { Indexer } from 'storybook/internal/types';
const urlIndexer: Indexer = {
test: /\.url\.js$/,
createIndex: async (fileName, { makeTitle }) => {
const fileData = await import(fileName);
return Object.entries(fileData)
.filter(([key]) => key != 'default')
.map(([name, url]) => {
return {
type: 'docs',
importPath: fileName,
exportName: name,
title: makeTitle(name)
.replace(/([a-z])([A-Z])/g, '$1 $2')
.trim(),
__id: `url--${name}--${encodeURIComponent(url as string)}`,
tags: ['!autodocs', 'url'],
};
});
},
};
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|ts|svelte)', '../src/**/*.url.js'],
framework: {
name: '@storybook/svelte-vite',
options: {},
},
experimental_indexers: async (existingIndexers) => [urlIndexer, ...existingIndexers],
};
export default config;
```
Custom indexer implementation with JSON fixture data example
An example indexer that generates stories for components based on JSON fixture data, looking for `*.stories.json` files:
```ts
const jsonStoriesIndexer: Indexer = {
test: /\.stories\.json$/,
createIndex: async (fileName, { makeTitle }) => {
const content = JSON.parse(await fs.readFile(fileName, 'utf-8'));
const entries: IndexInput[] = [];
Object.entries(content).forEach(([componentName, componentData]: [string, any]) => {
const { componentPath, stories } = componentData;
Object.entries(stories).forEach(([storyName, storyConfig]: [string, any]) => {
entries.push({
type: 'story',
importPath: `virtual:jsonstories--${fileName}--${componentName}`,
exportName: storyName,
title: makeTitle(`${componentName}/${storyName}`),
});
});
});
return entries;
},
};
```
Vite plugin for JSON stories transformation example
A Vite plugin to transform JSON stories into CSF:
```ts
import type { PluginOption } from 'vite';
import fs from 'fs/promises';
function JsonStoriesPlugin(): PluginOption {
return {
name: 'vite-plugin-storybook-json-stories',
load(id) {
if (!id.startsWith('virtual:jsonstories')) {
return;
}
const [, fileName, componentName] = id.split('--');
const content = JSON.parse(fs.readFileSync(fileName));
const { componentPath, stories } = getComponentStoriesFromJson(content, componentName);
return `
import ${componentName} from '${componentPath}';
export default { component: ${componentName} };
${stories.map((story) => `export const ${story.name} = ${story.config};\n`)}
`;
},
};
}
```
importPath requirement for CSF files
The value of `importPath` in an `IndexInput` must resolve to a CSF file. Custom indexers are often necessary because the input is not CSF. Therefore, you will likely need to transpile the input to CSF so that Storybook can read it in the browser and render your stories. For Webpack-based projects, custom importPaths are not supported and you must transpile the source file to CSF and leave `importPath` empty to use the original `fileName`.
IndexInput type definition
IndexInput is an object representing a story to be added to the stories index with the following properties:
- `exportName` (Required): string - The export to add as an entry in the index
- `importPath` (Optional): string - Default: The original `fileName` passed to createIndex. The file to import from, e.g. the CSF file. Custom `importPath`s are only supported in Vite-based projects; Webpack-based projects require transpilation to CSF.
- `type` (Required): 'story' - The type of entry
- `subtype` (Experimental, Optional): 'story' | 'test' - Default: 'story'. The subtype of the story entry when type is 'story'. Use to mark an entry as a test.
- `rawComponentPath` (Optional): string - The raw path/package of the file that provides `meta.component`
- `metaId` (Optional): string - Default: Auto-generated from title. Custom id for meta of the entry. If specified, the export default (meta) in the CSF file must have a corresponding `id` property.
- `name` (Optional): string - Default: Auto-generated from exportName. The name of the entry.
- `tags` (Optional): string[] - Tags for filtering entries in Storybook and its tools
- `title` (Optional): string - Default: Auto-generated from the meta of importPath. Determines the location of the entry in the sidebar. Should use the `makeTitle` function from IndexerOptions when specifying.
- `__id` (Optional): string - Default: Auto-generated from title/metaId and exportName. Custom id for the story of the entry. If specified, the story in the CSF file must have a corresponding `__id` property.
IndexerOptions type definition
IndexerOptions has the following structure:
- `makeTitle` (Required): `(userTitle?: string) => string` - A function that takes a user-provided title and returns a formatted title for the index entry, which is used in the sidebar. If no user title is provided, one is automatically generated based on the file name and path.
createIndex function signature and parameters
The `createIndex` function has the type `(fileName: string, options: IndexerOptions) => Promise<IndexInput[]>`. It accepts a single CSF file and returns a list of entries to index. The `fileName` parameter is a string representing the name of the CSF file used to create entries to index. The `options` parameter is of type `IndexerOptions`.
Indexer test property
The `test` property is a required RegExp that is run against file names included in the `stories` configuration. It should match all files to be handled by this indexer.
Indexer type definition
An Indexer object has the following properties:
- `test` (Required): RegExp - A regular expression run against file names that should match all files to be handled by this indexer
- `createIndex` (Required): `(fileName: string, options: IndexerOptions) => Promise<IndexInput[]>` - Function that accepts a single CSF file and returns a list of entries to index
Indexer purpose and functionality
Indexers are responsible for building Storybook's index of stories—the list of all stories and a subset of their metadata like `id`, `title`, `tags`, and more. The index can be read at the `/index.json` route of your Storybook. Custom indexers allow you to customize how Storybook indexes and parses files into story entries, adding flexibility to how stories can be written and where they come from.
Indexers API function signature
Indexers are defined as a function with the type `(existingIndexers: Indexer[]) => Promise<Indexer[]>`. The function returns the full list of indexers, including the existing ones, which allows you to add your own indexer to the list or replace an existing one.
Transpiling to CSF architecture
The transpilation process works in two stages: (1) During indexing, Storybook uses the `stories` configuration to find files matching the indexer's `test` property, passes each to `createIndex` function to generate index entries; (2) During browser rendering, when a user navigates to a story URL, the builder plugin transpiles the source file to CSF and serves it, then the UI imports and renders the story from the CSF file using the `exportName` property.
experimental_indexers configuration property
The indexers API is experimental and must be specified by the `experimental_indexers` property of `StorybookConfig` in main.js|ts configuration.
makeTitle function usage for custom titles
When specifying a title in IndexInput, you must use the `makeTitle` function provided in `IndexerOptions` to use Storybook's default naming behavior. You should not specify a title most of the time, so that your indexer will use the default naming behavior. When you do specify a title, call `makeTitle(userTitle)` to format it properly.
JSON fixture data example for stories
Example JSON fixture data file format for generating stories:
```json
{
"Button": {
"componentPath": "./button/Button.jsx",
"stories": {
"Primary": {
"args": {
"primary": true
}
},
"Secondary": {
"args": {
"primary": false
}
}
}
},
"Dialog": {
"componentPath": "./dialog/Dialog.jsx",
"stories": {
"Closed": {},
"Open": {
"args": {
"isOpen": true
}
}
}
}
}
```
staticDirs with configuration objects
staticDirs can use configuration objects with from and to properties to define source and destination directories for static files.
staticDirs main configuration
The staticDirs field in main.js|ts configuration sets a list of directories of static files to be loaded by Storybook. It accepts an array of either strings or configuration objects. Type is (string | { from: string; to: string })[].
Vite publicDir interaction with staticDirs
When using Vite-based frameworks, additional directories may be copied to the build directory because of Vite's own static asset handling. You can set Vite's publicDir option to false to disable this behavior and prevent unintended directory copying.
swc main.js configuration key
The swc key in main.js|ts configuration customizes Storybook's SWC setup for Webpack-based projects. Its type is a function: (config: swc.Options, options: Options) => swc.Options | Promise<swc.Options>. It is enabled via the @storybook/addon-webpack5-compiler-swc addon and is supported on all frameworks except Angular, Create React App, Ember.js and Next.js.
SWC Options type in main.js
The Options type for the swc configuration key has the structure: { configType?: 'DEVELOPMENT' | 'PRODUCTION' }. The configType property is optional. There are other options that are difficult to document; developers should introspect the type definition for more information.
stories field in main.js|ts - type and requirement
The stories field is required in the main configuration. It accepts either an array of strings or StoriesSpecifier objects, or an async function that takes an array and returns an array. Type: (string | StoriesSpecifier)[] | async (list: (string | StoriesSpecifier)[]) => (string | StoriesSpecifier)[]
stories field purpose
The stories field configures Storybook to load stories from specified locations. The intention is to colocate a story file along with the component it documents, such as Button.ts and Button.stories.ts in the same directory.
picomatch glob syntax for stories
The glob patterns in the stories field use the syntax supported by picomatch. You can use this to implement different naming conventions for story files, though some addons may assume Storybook's default naming convention.
StoriesSpecifier interface definition
StoriesSpecifier is an object type with the following fields: directory (string, required) indicating where to start looking for story files relative to project root; files (string, optional, default '**/*.@(mdx|stories.@(js|jsx|mjs|ts|tsx))') a glob relative to the directory that matches filenames to load; titlePrefix (string, optional, default '') a prefix used when auto-titling stories.
stories loading order with array of globs
When using an array of globs, Storybook loads stories from the project as found by the glob patterns. Stories are loaded in the order they are defined in the array, which allows control over the order stories appear in the sidebar.
Custom logic for loading stories
You can implement custom logic in the stories field by using an async function. However, this may de-optimize or break Storybook's static analysis of the configuration file, which is used to improve performance.
tags field in main.js/ts configuration
The tags field is a configuration option in main.js or main.ts that allows you to define custom tags for your stories or alter the default configuration of built-in tags. The type is { [tagName: string]: { defaultFilterSelection?: 'include' | 'exclude' } }.
tags[tagName].defaultFilterSelection configuration
The defaultFilterSelection property within a tag configuration has type 'include' | 'exclude'. When set to 'include', stories with this tag are selected as included by default in the Storybook sidebar filter. When set to 'exclude', stories with this tag are selected as excluded by default and must be explicitly included by selecting the tag in the sidebar filter menu. If not set, the tag has no default selection.
tags[tagName] configuration
The tagName key within the tags configuration is a string that represents the name of the tag. This can be any static (not created dynamically) string, and can be either a built-in tag or a custom tag of your own design.
viteFinal Options type
The Options parameter for viteFinal has the type { configType?: 'DEVELOPMENT' | 'PRODUCTION' }. The configType property is optional and can be either 'DEVELOPMENT' or 'PRODUCTION'. There are other options available that are difficult to document, and the type definition should be introspected for more information.
viteFinal main config field
The viteFinal field in main.js/ts configuration is a function that customizes Storybook's Vite setup when using the Vite builder. It takes two parameters: config (Vite.InlineConfig) and options (Options), and returns either Vite.InlineConfig or Promise<Vite.InlineConfig>. The function signature is (config: Vite.InlineConfig, options: Options) => Vite.InlineConfig | Promise<Vite.InlineConfig>.