Storybook for everyone: CSF vs. MDX
Tags: storybook, react, designsystems, docs
Published: 2021-04-20 22:31:40

Today, I'm going to talk about [Storybook](https://storybook.js.org/) v6. It is such a great tool to design, build, document and test isolated components and organize a perfect component library.

The [Component Story Format (CSF)](https://storybook.js.org/docs/react/api/csf) is the recommended way of writing stories but more recently, Storybook introduced the option of writing stories using [MDX Format](https://storybook.js.org/docs/react/writing-docs/mdx) so we can easily document our components with a format that we are all very familiar with.

In this post I'll be covering both ways of writing stories with the aim of showing some benefits of both tools and let you choose what works best for your project.

I'll be using a simple `Avatar` component to serve as example and we'll be creating our stories based on it. I usually build my component libraries using React and Style Components so that's what we'll be using today too.

Our `Avatar` will look like this:

```js
import styled from "styled-components";
import PropTypes from "prop-types";

export default function Avatar({ src, size, className, alt }) {
  return <Image size={size} className={className} src={src} alt={alt} />;
}

const Image = styled.img`
  border-radius: 100%;
  height: ${(props) => sizes[props.size]};
  width: ${(props) => sizes[props.size]};
`;

const sizes = {
  small: "30px",
  medium: "60px",
  large: "160px",
};

Avatar.propTypes = {
  /**
  The display src of the avatar
  */
  src: PropTypes.string,
  /**
  The display size of the avatar
  */
  size: PropTypes.oneOf(Object.keys(sizes)),
  /**
  Preserve className for styling
  */
  className: PropTypes.string,
  /**
  Include alt tag for accessibility
  */
  alt: PropTypes.string,
};

Avatar.defaultProps = {
  size: "medium",
  src: "/defaultAvatar.svg",
  alt: "avatar",
};
```

For those new to Storybook, a story is composed of a Canvas, which shows our rendered component and a Docs Block, which are the building blocks of Storybook documentation pages. The `PropTypes` function will be used in our Docs Block later to show our `ArgsTable` with all the `args` (props) included in our component.

## Component Story Format (CSF)

Let's begin with Storybook's recommended syntax, CSF.

In CSF, stories are defined as ES6 modules. Consequently, each story is composed of a single default export and one or multiple named exports.

The default exports serves as a default structure for all the stories that we'll write for a component, in our case, the `Avatar`.

```js
// Avatar.stories.js/ts

import React from "react";
import Avatar from "../components/Avatar";

export default {
  title: "Components/Avatar",
  component: Avatar,
};
```

The named export is a function that describes how to render a component, so it would simply look like this:

```js
export const Default = () => <Avatar src="/defaultAvatar.svg" size="medium" />;
```

But we're usually building our component library for documentation purposes so we want to be able to interact with our story and check every use case. For this reason, it is quite handy to include `args` so we can benefit from the [Controls addon](https://storybook.js.org/docs/react/essentials/controls) and [Actions addon](https://storybook.js.org/docs/react/essentials/actions).

In the Storybook documentation they use the `.bind()` method to create a reusable template to pass the components `args` to each of the component's stories. This comes in handy when we're going to have multiple stories from a single component since it reduces code repetition.

```js
const Template = (args) => <Avatar {...args} />;

export const Default = Template.bind({});
Default.args = {
  src: "/defaultAvatar.svg",
  size: "medium",
  alt: "avatar",
};

export const Profile = Template.bind({});
Profile.args = {
  src: "/lauraAvatar.svg",
  size: "small",
  alt: "user profile",
};
```

But if you're working closely with designers or other colleagues that are not too comfortable or familiar with the `.bind()` method, passing `args` within each story is also good. We end up having a bit more repetition but the code is more readable and you can save yourself the JavaScript lesson.

```js
export const Default = (args) => <Avatar {...args} />;
Default.args = {
  src: "/defaultAvatar.svg",
  size: "medium",
  alt: "avatar",
};
```

Alright, so now that we have completed our `Avatar` story we should probably include some documentation for it. That's when using CSF can get a bit tricky.

To add a description in our main Story, we need to insert it inside the `parameters` object usign `parameters.docs.description.component` in our export default or `parameters.docs.description.story` for our named exports.

```js
export default {
  title: "Components/Avatar",
  component: Avatar,
  parameters: {
    component: Avatar,
    componentSubtitle:
      "An Avatar is a visual representation of a user or entity.",
    docs: {
      description: {
        component: "Some description",
      },
    },
  },
};
```

```js
const Template = (args) => <Avatar {...args} />;
export const Default = Template.bind({});
Default.args = {
  src: "/defaultAvatar.svg",
  size: "medium",
  alt: "avatar",
};
export const Profile = Template.bind({});
Profile.args = {
  src: "/lauraAvatar.svg",
  size: "small",
  alt: "user profile",
};
Profile.parameters = {
  docs: {
    description: {
      story: `This is a story`,
    },
  },
};
```

As you can see, this is a bit of a tedious way of writing documentation.

## MDX

Writing stories with MDX fixes the previous issue. It allows anyone familiar with the simple Markdown `.md` format to write documentation. The main benefit is that now non-technical team members can be a part of documenting component libraries.

Designers can now share their design tokens and write documentation to make developers understand the reason behind design patterns. Here is a very cool example from the Storybook MDX announcement [article](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc). Philip Siekmann created an [amazing addon](https://github.com/UX-and-I/storybook-design-token) that generates design token documentation from your stylesheets and asset files and the [demo file](https://storybook-design-token-v1.netlify.app/?path=/story/components-button--button) is documented using MDX.

Imagine that in our `Avatar` story we want to include documentation that ensure our component's best practices. MDX makes this super easy.

```js
import { Meta, Story, Canvas, ArgsTable } from "@storybook/addon-docs/blocks";
import Avatar from "../components/Avatar";

<Meta
  title="Components/Avatar"
  component={Avatar}
  argTypes={{
    src: {
      name: "source",
      control: { type: "text" },
    },
    size: {
      name: "size",
      defaultValue: "medium",
      control: {
        type: "select",
        options: ["small", "medium", "large"],
      },
    },
  }}
/>

# Avatar

An `Avatar` is a visual representation of a user or entity.

The `small` size should only be used inside a `navbar`.

We should always make sure that we provide an alternative text for screen readers. Therefore, always ensure that the `alt` tag is being used in the component.

<Canvas>
  <Story
    name="Default"
    args={{
      src: "/defaultAvatar.svg",
      size: "medium",
      alt: "default"
    }}>
    {(args) => <Avatar {...args} />}
  </Story>
</Canvas>;

<ArgsTable of={Avatar} />
```

The `ArgsTable` is going to render a table in the Docs Blocks with all of the available `args`. Notice that when using MDX even if we have the Controls addon we can't interact with the component's arguments dynamically in the Docs Blocks but we can customise this table using [`argsType`](https://storybook.js.org/docs/react/api/argtypes) inside the `<Meta />` component.

As you can see, the MDX structure is very similar to CSF since in the back, Storybook will compiled the MDX files into CSF.

## Source Dynamic code snippet

If you're using `decorators` in your component, you might have encountered something like this inside the source code snippet:

```js
// CSF
<div
  style={{
    margin: "2em",
  }}
>
  <No Display Name />
</div>
```

```js
// MDX
<MDXCreateElement
  mdxType="div"
  originalType="div"
  style={{
    margin: "2em",
  }}
>
  <No Display Name />
</MDXCreateElement>
```

Decorators provide extra "external" information about a component like extra markup for styling, providing side-loaded data or including required context as `ThemeProvider`. We can use them globally but also render decorators individually inside each component. When used locally inside our component, these decorators are causing some issues inside the source code snippet.

There are plans of fixing this in the next version but currently, you can use a workaround changing the source code to `code` using `parameters.docs.source.type`.

```js
export default {
  title: "Components/Avatar",
  component: Avatar,
  parameters: {
    docs: {
      source: {
        type: "code",
      },
    },
  },
};
```

I have created a [PR for Storybook](https://github.com/storybookjs/storybook/pull/14652) in an attempt to fix this, so it would be great to get any feedback from you all!

## Conclusion

Both CSF and MDX provide a great way of building component libraries. Choosing one over the other mainly depends on your team structure or the usage your planning on having from your component library.

I recently ran a [twitter poll](https://twitter.com/lcarb14/status/1379913918445801473) on prefered method when writing stories and nearly 70% of the people (80 votes aprox.) voted on using CSF, which is understandable as it is still the standard and recommended way. But, MDX is still a very convenient way of writing stories in those cases where CSF seems a bit of a barrier for non technical users or our component needs precise and well structured documentation.

In my opinion you should not pick between CSF or MDX, they are both a great way of writing stories and both work best when complementing each other.