Skip to content

Anatomy of a Story

Edwin Guzman edited this page Jun 28, 2024 · 6 revisions

This for Storybook v8.x.

This is a deep dive into how we write and structure a story for a component. To find out more about a component, check out the Anatomy of a Component page.

When creating stories, we want to document as much as possible for the component itself and how to use the component with other DS components or other libraries. More is better. The goal for maintainers and contributors is to write consistent-looking stories and documentation that other developers can use to easily figure out how to implement a component or pattern in their project.

Using MDX and Controls

Currently, many stories are written using Markdown Documentation(MDX) and Controls to dynamically update a component's props.

Story Structure

When writing a story, two files must be created. The .mdx file contains the Storybook documentation blocks and the .stories.tsx file contains the React code including the DS component examples.

.mdx file

The following block format is the convention used for documenting DS components in the .mdx file.

  • <Meta>
    • Each file must contain this Storybook component and include an of prop to define the component it's documenting.
  • Title
    • An h1 # heading
  • Component Version Table
    • This table contains the DS version where the component was first added and where it was last updated.
  • Table of Contents
    • List of all the headings in the component's story. This always includes Overview, Component Props, and Accessibility in that order as well as Changelog as the last section. The rest of the headings are based on props that should be highlighted and examples.
  • Overview
    • This mandatory first heading contains the general description of the component.
    • Declare <Description of={...}> to use the component's code comment block for its description.
  • Component Props
    • This mandatory heading contains the main Canvas block with of set to controls in Storybook's "Canvas" section.
    • This section also declares <ArgsTable of="..."> to display the comment block for every prop in the component’s interface.
  • Accessibility
    • This mandatory heading contains accessibility information about the component.
    • Make sure this also contains a list of "Resources" linking to relevant accessibility resources. Link credible resources such as from W3C or Deque.
  • Optional Headings
    • If additional headings were declared in the "Table of Contents", those headings must be written here. Typically, they describe component variants, component props, and component examples.
    • Here's where the <Canvas> component and text can be used to render patterns and code blocks to resemble the component examples from the relevant Figma designs.
  • Changelog
    • This section renders the ComponentChangelogTable helper component to display a changelog table for the component.

Code Example

The following example is shortened for brevity. For more details, see the full Button.mdx example.

Button.mdx

import { ArgTypes, Canvas, Description, Meta } from "@storybook/blocks";
import { changelogData } from "./buttonChangelogData";
import ComponentChangelogTable from "../../utils/ComponentChangelogTable";

import * as ButtonStories from "./Button.stories";
import Link from "../Link/Link";

<Meta of={ButtonStories} />

# Button

| Component Version | DS Version |
| ----------------- | ---------- |
| Added             | `0.0.4`    |
| Latest            | `3.1.1`    |

## Table of Contents

- {<Link href="#overview" target="_self">Overview</Link>}
- {<Link href="#component-props" target="_self">Component Props</Link>}
- {<Link href="#accessibility" target="_self">Accessibility</Link>}
- {<Link href="#button-groups" target="_self">Button Groups</Link>}
- {<Link href="#changelog" target="_self">Changelog</Link>}

## Overview

<Description of={ButtonStories} />

Update the `buttonType` in the Controls section to see the types of buttons
that can be rendered.

- `"primary"` is used for actions that move the user forward. This is the default.
- `"secondary"` is used for actions that move the user back, such as cancellations.
- `"text"` is used for equally weighted actions in a text based list.
// ...

## Component Props

<Canvas of={ButtonStories.WithControls} />

<ArgTypes of={ButtonStories.WithControls} />

## Accessibility

The Reservoir `Button` component renders an HTML `<button>` element that is
accessible.
// ...

Resources:

- [W3C WAI ARIA Button](https://www.w3.org/TR/wai-aria-practices-1.1/#button)
- [W3C WAI ARIA Button Examples](https://www.w3.org/TR/wai-aria-practices-1.1/examples/button/button.html)

## Button Groups

Primary and secondary buttons should be grouped like in the following example.
The `ButtonGroup` component should be used to wrap the `Button` component.

<Canvas of={ButtonStories.ButtonGroups} />

// ...

## Changelog

<ComponentChangelogTable changelogData={changelogData} />

.stories.tsx file

The following is the convention used for creating DS components Stories in the .stories.tsx file.

  • meta object
    • This is typed using Storybook's Meta type with the component's type as input.
    • This contains title, component, and argTypes properties.
    • The title organizes the component in the desired category in the Storybook site's sidebar.
    • The argTypes varies per component and here is where we can control if a prop is enabled in the "Controls" section, how it should render, and what option values are available.
    • This must be the default export for Storybook to add to its site.
  • Story type
    • This type is created based on Storybook's StoryObj type. This helps declare each component block when it's rendered on the page.
  • WithControls story
    • Our convention is to name the "main" story block WithControls. This is always the first story declared and exported after the meta object. This sets the With Controls example as the first story in Storybook's sidebar component section. This is the dynamic story block that users can controls in Storybook.
  • Other Story examples
    • Any additional stories can be created based on how the props or examples should be documented. For example, does a component have five variants? A single Story can be created that renders all five variants together. These examples are always static and we don't want users to change them so they are NOT connected to Storybook's "Controls".

Code Example

The following example is shortened for brevity. For more details, see the full Button.stories.tsx example.

import { Box, VStack } from "@chakra-ui/react";
import type { Meta, StoryObj } from "@storybook/react";

import Button from "../Button/Button";
import ButtonGroup, { buttonGroupWidthsArray } from "./ButtonGroup";
import Heading from "../Heading/Heading";

const meta: Meta<typeof ButtonGroup> = {
  title: "Components/Form Elements/ButtonGroup",
  component: ButtonGroup,
  argTypes: {
    buttonWidth: {
      control: { type: "radio" },
      options: buttonGroupWidthsArray,
      table: { defaultValue: { summary: "default" } },
    },
    className: { control: false },
    id: { control: false },
    isDisabled: { table: { defaultValue: { summary: false } } },
    layout: {
      control: { type: "radio" },
      options: ["column", "row"],
      table: { defaultValue: { summary: "row" } },
    },
  },
};

export default meta;
type Story = StoryObj<typeof ButtonGroup>;

/**
 * Main Story for the ButtonGroup component. This must contains the `args`
 * and `parameters` properties in this object.
 */
export const WithControls: Story = {
  args: {
    buttonWidth: "default",
    className: undefined,
    id: "button-id",
    isDisabled: false,
    layout: "row",
  },
  parameters: {
    jest: ["ButtonGroup.test.tsx"],
  },
  render: (args) => (
    <ButtonGroup {...args}>
      <Button buttonType="secondary" id="group-1">
        Button
      </Button>
      <Button id="group-2">Submit</Button>
    </ButtonGroup>
  ),
};

// The following are additional ButtonGroup example Stories.
export const Width: Story = {
  render: () => (
    <VStack align="left" spacing="l">
      // ...
    </VStack>
  ),
};

// ...