mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:21:17 +08:00
291 lines
13 KiB
Plaintext
291 lines
13 KiB
Plaintext
---
|
||
title: 'How to write stories'
|
||
sidebar:
|
||
order: 2
|
||
title: Stories
|
||
---
|
||
|
||
<YouTubeCallout id="P0WHt_L0-2g" title="How to write Storybook stories | Component Story Format" />
|
||
|
||
<If renderer="svelte">
|
||
|
||
A story captures the rendered state of a UI component. Given a set of arguments, it can be a simple object with annotations or a component that describes its behavior and appearance.
|
||
|
||
</If>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
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.
|
||
|
||
</If>
|
||
|
||
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
|
||
|
||
<If renderer="svelte">
|
||
|
||
We can define stories according to the [Component Story Format](../api/csf.mdx) (CSF), an ES6 module-based standard that is easy to write and portable between tools, or rely on the community-led project [`Svelte CSF`](https://storybook.js.org/addons/@storybook/addon-svelte-csf) which provides a similar experience.
|
||
|
||
With Svelte CSF, the essential elements are the `defineMeta` function, which describes the component, and the `Story` component, which describes the stories. This pattern is different from the standard CSF, which uses a [default export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Using_the_default_export) and [named exports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Using_named_exports) to apply the same concepts.
|
||
|
||
</If>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
We define stories according to the [Component Story Format](../api/csf.mdx) (CSF), an ES6 module-based standard that is easy to write and portable between tools.
|
||
|
||
The key ingredients are the [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.
|
||
|
||
</If>
|
||
|
||
### Default export
|
||
|
||
<If renderer="svelte">
|
||
|
||
The `defineMeta` function in Svelte CSF with native templating syntax controls how Storybook lists your stories and provides information used by addons. However, if you're not using this story format and relying on standard CSF, use the *default* export to achieve the same result. Below is an example of a story file with both approaches:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-default-export-with-component.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
<Callout variant="info">
|
||
|
||
Starting with Storybook version 7.0, story titles are analyzed statically as part of the build process. With Svelte, the `defineMeta` function or *default* export must contain a `title` property that can be read statically. Using the `id` property to customize your story URL must also be statically readable.
|
||
|
||
</Callout>
|
||
|
||
</If>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
The *default* export metadata controls how Storybook lists your stories and provides information used by addons. For example, here’s the default export for a story file `Button.stories.js|ts`:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-default-export-with-component.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
<Callout variant="info">
|
||
|
||
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.
|
||
|
||
</Callout>
|
||
|
||
</If>
|
||
|
||
### Defining stories
|
||
|
||
<If renderer="svelte">
|
||
|
||
If you're using Svelte CSF, define your stories with the `Story` component, otherwise use the named exports of a standard CSF file. 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` using both methods.
|
||
|
||
</If>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
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`.
|
||
|
||
</If>
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-with-args.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
<IfRenderer renderer="react">
|
||
#### 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](./args.mdx) 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:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
</IfRenderer>
|
||
|
||
<IfRenderer renderer="solid">
|
||
#### Working with Solid Signals
|
||
|
||
[Solid Signals](https://www.solidjs.com/docs/latest/api#basic-reactivity) 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](./args.mdx) as much as possible when writing your own stories. As an example, here’s a story that uses Solid Signals to change the button's state:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
</IfRenderer>
|
||
|
||
### Rename stories
|
||
|
||
You can rename any particular story you need. For instance, to give it a more accurate name. Here's how you can change the name of the `Primary` story:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-rename-story.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
Your story will now be shown in the sidebar with the given text.
|
||
|
||
{/* Maintaining a prior heading */}
|
||
|
||
<a id="using-args" />
|
||
|
||
## How to write stories
|
||
|
||
|
||
<IfRenderer renderer="svelte">
|
||
|
||
With Svelte, stories can be defined as objects using standard CSF or with Svelte CSF's `Story` component. Both methods describe 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 above.
|
||
|
||
</IfRenderer>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
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.
|
||
|
||
</If>
|
||
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-using-args.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
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`.
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-group-story.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
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](../essentials/controls.mdx) panel. It means your team can dynamically change components in Storybook to stress test and find edge cases.
|
||
|
||
<Video src="../_assets/writing-stories/addon-controls-demo-optimized.mp4" />
|
||
|
||
You can also use the Controls panel to edit or save a new story after adjusting its control values.
|
||
|
||
<Video src="../_assets/get-started/edit-story-from-controls-optimized.mp4" />
|
||
|
||
<If renderer="svelte">
|
||
|
||
<Callout variant="info">
|
||
|
||
This feature is not supported with the Svelte CSF. To opt-in to this feature with Svelte, you must use Storybook's [Component Story Format](../api/csf.mdx).
|
||
|
||
</Callout>
|
||
|
||
</If>
|
||
|
||
Addons can enhance args. For instance, [Actions](../essentials/actions.mdx) auto-detects which args are callbacks and appends a logging function to them. That way, interactions (like clicks) get logged in the actions panel.
|
||
|
||
<Video src="../_assets/writing-stories/addon-actions-demo-optimized.mp4" />
|
||
|
||
### Using the play function
|
||
|
||
Storybook's `play` function and the [`@storybook/addon-interactions`](https://storybook.js.org/addons/@storybook/addon-interactions) are 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:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="login-form-with-play-function.md" />
|
||
|
||
Without the help of the `play` function and the `@storybook/addon-interactions`, you had to write your own stories and manually interact with the component to test out each use case scenario possible.
|
||
|
||
### 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:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="parameters-in-meta.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||

|
||
|
||
This parameter would instruct the backgrounds addon to reconfigure itself whenever a Button story is selected. Most addons are configured via a parameter-based API and can be influenced at a [global](./parameters.mdx#global-parameters), [component](./parameters.mdx#component-parameters) and [story](./parameters.mdx#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.
|
||
|
||
<IfRenderer renderer="svelte">
|
||
|
||
A simple example is adding padding to a component’s stories. With Svelte, you can either use an auxiliary component to wrap your stories with the required spacing or layout elements, or ignore the concept of decorators entirely and define them inline in a template.
|
||
|
||
{ /* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="margindecorator.md" />
|
||
|
||
{ /* prettier-ignore-end */}
|
||
|
||
|
||
</IfRenderer>
|
||
|
||
<If notRenderer="svelte">
|
||
|
||
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:
|
||
|
||
</If>
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="button-story-component-decorator.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
Decorators [can be more complex](./decorators.mdx#context-for-mocking) and are often provided by [addons](../configure/user-interface/storybook-addons.mdx). You can also configure decorators at the [story](./decorators.mdx#story-decorators), [component](./decorators.mdx#component-decorators) and [global](./decorators.mdx#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.
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="list-story-starter.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
In such cases, it makes sense to render a different function for each story:
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="list-story-expanded.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
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.
|
||
|
||
{/* prettier-ignore-start */}
|
||
|
||
<CodeSnippets path="list-story-reuse-data.md" />
|
||
|
||
{/* prettier-ignore-end */}
|
||
|
||
<Callout variant="info" icon="💡">
|
||
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](../writing-stories/stories-for-multiple-components.mdx) workflow documentation.
|
||
</Callout>
|