StorybookJS – Stories

[Fuente: https://storybook.js.org/docs/angular/get-started/whats-a-story]

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.

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 or .stories.ts. The stories are written in Component Story Format (CSF)–an ES6 modules-based standard–for writing component examples.

Let’s start with the Button component. A story is a function 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.

View the rendered Button by clicking on it in the Storybook sidebar.

The above story definition can be further improved to take advantage of Storybook’s “args” concept. Args describes the arguments to Button in a machine readable way. This unlocks Storybook’s superpower of altering and composing arguments dynamically.

// We create a “template” of how args map to rendering

const Template = (args) => <Button {...args} />;

// Each story then reuses that template
export const Primary = Template.bind({});

Primary.args = {
  primary: true,
  label: 'Button',
};

Both story examples render the same thing because Storybook feeds the given args property into the story during render. But you get timesaving conveniences with args:

  • Buttons callbacks are logged into the Actions tab. Click to try it.
  • Buttons arguments are dynamically editable in the Controls tab. Adjust the controls

Note that Template.bind({}) is a standard JavaScript technique for making a copy of a function. We copy the Template so each exported story can set its own properties on it.

Edit a story

Storybook makes it easy to work on one component in one state (aka a story) at a time. When you edit the Button code or stories, Storybook will instantly re-render in the browser. No need to manually refresh.

Stories are also useful for checking that 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 a component’s stories as you develop helps prevent accidental regressions. Tools that integrate with Storybook can also automate this for you.

Browse Stories

Sidebar and Canvas

A *.stories.js file defines all the stories for a component. Each story has a corresponding sidebar item. When you click on a story it renders in the Canvas, an isolated preview iframe.

Navigate between stories by clicking on them in the sidebar or use keyboard shortcuts (for instance use opt/alt + ◀️ ▶️). Try the sidebar search to find a story by name.

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.
📱 Viewport renders the component in a variety of dimensions and orientations. It’s ideal for checking the responsiveness of components.

The “Docs” tab shows auto-generated documentation about components (inferred from the source code). Usage docs are helpful when sharing reusable components with your team. For example, in a design system or component library.

The toolbar is customizable. You can use globals to quickly toggle themes and languages. Or install Storybook toolbar addons 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.

Controls allows you to dynamically interact with a component’s args (inputs). 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.

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.

In the next chapter, we’ll get your components rendering in Storybook so you can use it to supercharge component development.

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 file to go along with it. It might look something like this:

Button.stories.ts

// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1
import { Story, Meta } from '@storybook/angular/types-6-0';
import Button from './button.component';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
} as Meta;

const Template: Story<Button> = (args: Button) => ({
  component: Button,
  props: args,
});

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button'
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

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.

Configure Storybook for your stack

Storybook comes with a permissive default configuration. 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
  • Runtime configuration
  • Component context

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 tips on what could help:

  • CSS-in-JS like styled-components and Emotion
  • @import CSS into components
  • Global imported styles
  • Add external CSS or fonts in the <head>

Load assets and resources

If you want to link to static files in your project or stories (e.g. /fonts/XYZ.woff), use the -s path/to/folder to specify a static folder to serve from when you start up Storybook. To do so, edit the storybook and build-storybook scripts in package.json.

We recommend serving external resources and assets requested in your components statically with Storybook. This ensures that assets are always available to your stories.

How to write stories

A story captures the rendered state of a UI component. It’s a function that returns a component’s state given a set of arguments.

Storybook uses the generic term arguments (args for short) when talking about React’s props, Vue’s slots, 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, it won’t be included in your production bundle.

Button.js | ts
Button.stories.js | ts

Component Story Format

We define stories according to the Component Story Format (CSF), an ES6 module-based standard that is easy to write and portable between tools.

The key ingredients are the default export that describes the component, and 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 default export for a story file Button.stories.js:

// Button.stories.ts

import Button from './button.component';

export default {
  title: 'Components/Button',
  component: Button,
}

Defining stories

Use the named exports of a CSF file to define your component’s stories. Here’s how to render Button in the “primary” state and export a story called Primary.

// Button.stories.ts

import Button from './button.component';

export const Primary = () => ({
  component: Button,
  props: {
    label: 'Button',
  },
});

Rename stories

You can rename any particular story you need. For instance to give it a more clearer name. Here’s how you can change the name of the Primary story:

// Button.stories.ts

import Button from './button.component';

export const Primary = () => ({
  component: Button,
  props: {
    label: 'Button',
  },
});

Primary.storyName='I am the primary';

Your story will now be shown in the sidebar with the given text.

How to write stories

A story is a function that describes how to render a component. You can have multiple stories per component. The simplest way to create stories is to render a component with different arguments multiple times.

// Button.stories.ts

import Button from './button.component';

export const Primary = () => ({
  component: Button,
  props: {
    label: 'Button',
    background="#ff0",
  },
});

export const Secondary = () => ({
  component: Button,
  props: {
    label: '😄👍😍💯',
    background="#ff0",
  },
});

export const Tertiary= () => ({
  component: Button,
  props: {
    label: '📚📕📈🤓',
    background="#ff0",
  },
});

This is straightforward for components with few stories, but can be repetitive with many stories.

Using args

Refine this pattern by defining a master template for a component’s stories that allows you to pass in args. This reduces the unique code you’ll need to write and maintain for each story.

// Button.stories.ts

// We create a “template” of how args map to rendering
const Template = (args: Button) => ({
  component: Button,
  props: args,
});

export const Primary = Template.bind({});
Primary.args = { background="#ff0",  label: 'Button' };

export const Secondary = Template.bind({});
Secondary.args = {  ...Primary.args,  label: '😄👍😍💯',};

export const Tertiary = Template.bind({});
Tertiary.args = {  ...Primary.args,  label: '📚📕📈🤓',};

The template is reused across stories. Template.bind({}) makes a copy of the function which reduces code duplication. Similarly,…Primary.args makes a copy of the data, reducing data duplication.

What’s more, you can import args to reuse when writing stories for other components. This is useful when you’re building composite components. For example, if we make a ButtonGroup story, we might remix two stories from its child component Button.

// ButtonGroup.stories.ts

import { moduleMetadata } from '@storybook/angular';
import { CommonModule } from '@angular/common';

import ButtonGroup from './ButtonGroup.component';
import Button from './button.component';
import * as ButtonStories from './Button.stories';

export default {
  title: 'ButtonGroup',
  component: ButtonGroup,
  decorators: [
    moduleMetadata({
      declarations: [Button],
      imports: [CommonModule],
    }),
  ],
};

const Template = (args: ButtonGroup) => ({
  component :ButtonGroup,
  props: args,
});

export const Pair = Template.bind({});
Pair.args = {
  buttons: [ ...ButtonStories.Primary.args, ...ButtonStories.Secondary.args ],
  orientation: 'horizontal',
};

When Button’s signature changes, you only need to change Button’s stories to reflect the new schema. ButtonGroup’s stories will automatically be updated. This pattern allows you to reuse your data definitions up and down your 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 panel. This means your team can dynamically change components in Storybook to stress test and find edge cases.

Addons can enhance args. For instance, Actions 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 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:

// Button.stories.ts

import Button from './button.component';

export default {
  title: 'Button',
  component: Button,
  parameters: {
    backgrounds: {
      values: [
         { name: 'red', value: '#f00', },
         { name: 'green', value: '#0f0', },
         { name: 'blue', value: '#00f', },
      ]
    }
  }
}

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, component and story 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 certain 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:

// Button.stories.js

import React from 'react';
import Button from './Button';

export default {
  title: "Button",
  component: Button,
  decorators:  [(Story) => <div style={{ margin: '3em' }}><Story/></div>]
};

Decorators can be more complex and are often provided by addons. You can also configure decorators at the story, component and global level.

Stories for two or more components

When building design systems or component libraries, you may have two or more components that are designed to work together. For instance, if you have a parent List component, it may require child ListItem components.

// List.stories.ts

import List from './list.component';

export default {
  title: 'List',
  component: List,  
};

// Always an empty list, not super interesting
const Template = (args: List) => ({
  component: List,
  props: args,
});

In such cases, it makes sense to render a different function for each story:

// List.stories.ts

import { moduleMetadata } from '@storybook/angular';
import { CommonModule } from '@angular/common';

import List from './list.component';
import ListItem from './list-item.component'


export default {
  title: 'List',
  component: List,
  decorators: [
    moduleMetadata({
      declarations: [ListItem],
      imports: [CommonModule],
    }),
  ],
};

// Always an empty list, not super interesting
const Template = (args: List) => ({
  component: List,
  props: args,
});

export const OneItem = Template.bind({});
OneItem.args = {
    items:[ListItem]
};

export const ManyItems = Template.bind({});
ManyItems.args = {
    items:[ListItem,ListItem,ListItem]
};

You can also reuse stories from the child ListItem in your List component. That’s easier to maintain because you don’t have to keep the identical story definitions up to date in multiple places.

// List.stories.ts

import * as ListItemStories from './ListItem.stories';

export const ManyItems = Template.bind({});
ManyItems.args = {
    items:[
        ...ListItemStories.Selected.args,
        ...ListItemStories.Unselected.args,
        ...ListItemStoriesUnselected.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 more complex composite components. For more discussion, see the multi component stories workflow article.

Args

A story is a component with a set of arguments (props, slots, inputs, etc). “Args” are Storybook’s mechanism for defining those arguments as a first class entity that’s machine readable. This allows Storybook and its addons to live edit components. You do not need to change your underlying component code to use args.

When an arg’s value is changed, 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. For details on how args work, read on.

Args object

The args object can be defined at the story and component level (see below). It is an object with string keys, where values can have any type that is allowed to be passed into a component in your framework.

Story args

To define the args of a single story, use the args CSF story key:

//Button.stories.ts

const Template = (args: Button) => ({
  component: Button,
  props: args,
});

export const Primary = Template.bind({});

Primary.args = {
  primary: true,
  label: 'Primary',
};

These args will only apply to the story for which they are attached, although you can reuse them via JavaScript object reuse:

// Button.stories.js

export const PrimaryLongName = Template.bind({});

PrimaryLongName.args = {
  ...Primary.args,
  label: 'Primary with a really long name',
}

In the above example, we use the object spread feature of ES 2015.

Component args

You can also define args at the component level; such args will apply to all stories of the component unless they are overwritten. To do so, use the args key of the default CSF export:

// Button.stories.ts

import Button from './button.component';

export default {
  title: "Button",
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  args: {
    // Now all Button stories will be primary.
    primary: true,
  },
}

Args composition

You can separate the arguments to a story to compose in other stories. Here’s how args can be used in multiple stories for the same component.

// Button.stories.js

const Primary = ButtonStory.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
}

const Secondary = ButtonStory.bind({});
Secondary.args = {
  ...Primary.args,
  primary: false,
}

Note that if you are doing the above often, you may want to consider using component-level 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:

// Page.stories.ts

import { moduleMetadata } from '@storybook/angular';
import { CommonModule } from '@angular/common';

import Button from './button.component';
import Header from './header.component';
import Page from './page.component';

import * as HeaderStories from './Header.stories';

export default {
  title: 'Example/Page',
  component: Header,
  decorators: [
    moduleMetadata({
      declarations: [Button, Header],
      imports: [CommonModule],
    }),
  ],
};

const Template = (args: Page) => ({
  component: Page,
  props: args,
});

export const LoggedIn = Template.bind({});
LoggedIn.args = {
  ...HeaderStories.LoggedIn.args,
};

 

How to document components

When you write component stories during development, you also create basic documentation to revisit later.

Storybook gives you tools to expand this basic documentation with prose and layout that feature your components and stories prominently. That allows you to create UI library usage guidelines, design system sites, and more.

Out of the box, Storybook ships with DocsPage, a documentation template that lists all the stories for a component and associated metadata. It infers metadata values based on source code, types and JSDoc comments.  Customize this page to create a new template if you have specific requirements.

You can also create free-form pages for each component using MDX, a format for simultaneously documenting components and writing stories.

In both cases, you’ll use Doc Blocks as the building blocks to create full featured documentation.

Docs is autoconfigured to work out of the box in most use cases. In some cases you may need or want to tweak the configuration.

DocsPage

When you install Storybook Docs, DocsPage is the zero-config default documentation that all stories get out of the box. It aggregates your stories, text descriptions, docgen comments, args tables, and code examples into a single page for each component.

The best practice for docs is for each component to have its own set of documentation and stories.

Component parameter

Storybook uses the component key in the story file’s default export to extract the component’s description and props.

// MyComponent.stories.js

import { MyComponent } from './MyComponent';

export default {
  title: 'MyComponent',
  component: MyComponent,
};
// your templates and stories

Subcomponents parameter

Sometimes it’s useful to document multiple components together. For example, a component library’s ButtonGroup and Button components might not make sense without one another.

DocsPage has the concept of a “primary” component that is defined by the component parameter. It also accepts one or more subcomponents.

// ButtonGroup.stories.ts

import { ButtonGroup, ButtonGroupProps } from '../ButtonGroup';

export default {
  title: 'Path/to/ButtonGroup',
  component: ButtonGroup,
  subcomponents: { Button },
} as Meta;

Subcomponent ArgsTables will show up in a tabbed interface along with the primary component. The tab titles will correspond to the keys of the subcomponents object.

If you want to organize your documentation differently for component groups, we recommend using MDX. It gives you complete control over display and supports any configuration.

Replacing DocsPage

Replace DocsPage template with your own for the entire Storybook, a specific component, or a specific story.

Override the docs.page parameter:

  • With null to remove docs.
  • With MDX docs.
  • With a custom component

Story-level

Override the docs.page parameter in the story definition.

// Button.stories.js

export const Primary = Template.bind({});
Primary.parameters = { docs: { page: null } }

Component-level

Override the docs.page parameter in the default export of the story file.

// Button.stories.js

import { Button } from './Button';

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  parameters: { 
    docs: { 
      page: null 
    } 
  },
};

Global-level

Override the docs.page parameter in .storybook/preview.js.

// .storybook/preview.js

export const parameters { docs: { page: null } });

Remixing DocsPage using doc blocks

Doc blocks are the basic building blocks of Storybook Docs. DocsPage composes them to provide a reasonable UI documentation experience out of the box.

If you want to make minor customizations to the default DocsPage but don’t want to write your own MDX you can remix DocsPage. That allows you to reorder, add, or omit doc blocks without losing Storybook’s automatic docgen capabilities.

Here’s an example of rebuilding DocsPage for the Button component using doc blocks:

// Button.stories.tsx

import React from 'react';

import { Story, Meta } from '@storybook/react/types-6-0';

import {
  Title,
  Subtitle,
  Description,
  Primary,
  Props,
  Stories,
  PRIMARY_STORY,
} from '@storybook/addon-docs/blocks';

import { Button, ButtonProps } from './Button';

export default {
  title: 'Button',
  component: Button,
  parameters: {
    docs: {
      page: () => (
        <>
          <Title />
          <Subtitle />
          <Description />
          <Primary />
          <ArgsTable story={PRIMARY_STORY} />
          <Stories />
        </>
      ),
    },
  },
} as Meta;

 

Apply a similar technique to remix the DocsPage at the story, component, or global level.

In addition, you can interleave your own components to customize the auto-generated contents of the page, or pass in different options to the blocks to customize their appearance. Read more about Doc Blocks.

Story file names
Unless you use a custom webpack configuration, all of your story files should have the suffix *.stories.@(j|t)sx?. For example, “Badge.stories.js” or “Badge.stories.tsx”. This tells Storybook and its docs preset to display the docs based on the file contents.

Inline stories vs. iframe stories
DocsPage displays all the stories of a component in one page. You have the option of rendering those stories inline or in an iframe.

By default, we render React and Vue stories inline. Stories from other supported frameworks will render in an <iframe> by default.

The iframe creates a clean separation between your code and Storybook’s UI. But using an iframe has disadvantages. You have to explicitly set the height of iframe stories or you’ll see a scroll bar. And certain dev tools might not work right.

Render your framework’s stories inline using two docs configuration options in tandem, inlineStories and prepareForInline.

Setting inlineStories to true tells Storybook to stop putting your stories in an iframe. The prepareForInline accepts a function that transforms story content from your given framework to something React can render (Storybook’s UI is built in React).

Different frameworks will need to approach this in different ways. Angular, for example, might convert its story content into a custom element (you can read about that here).

Here’s an example of how to render Vue stories inline. The following docs config block uses prepareForInline along with an effect hook provided by @egoist/vue-to-react.

// .storybook/preview.js

import React from ‘react’;
import { render } from ‘react-dom’;
import toReact from ‘@egoist/vue-to-react’;

export const parameters = {
docs: {
prepareForInline: (storyFn, { args }) => {
const Story = toReact(storyFn());
return <Story {…args} />;
},
},
};
Copy
With this function, anyone using the docs addon for @storybook/vue can make their stories render inline, either globally with the inlineStories docs parameter, or on a per-story-basis using the inline prop on the <Story> doc block.

If you come up with an elegant and flexible implementation for the prepareForInline function for your own framework, let us know. We’d love to make it the default configuration to make inline stories more accessible for a larger variety of frameworks!