🙂

User-friendly documentation for your component library with Storybook

With the help of @storybook/addon-docs you can display the foundations of the code such as design token variables. The own Storybook design system is a good place to explore how the addon's blocks can be used for documenting the basic parts of a design system usually expressed as design tokens or variables. For example, the colors can be shown as a palette.

import { ColorPalette, ColorItem } from '@storybook/addon-docs/blocks';

export const Palette = () => (
	<ColorPalette>
  <ColorItem title="Primary" subtitle="Pink" colors="#F74D7B" />
  <ColorItem title="Secondary" subtitle="Dark" colors="#15325D" />
  <ColorItem title="Tertiary" subtitle="Blue" colors="#26ABE7" />
</ColorPalette>
)

Similarly, other foundation elements can be displayed with the help of suitable blocks. You can also go with <IconGallery> and <IconItem> for iconography, and nicely represent the fonts with the <Typeset>. Unfortunately, the documentation for the blocks is yet missing but the blog post "Rich docs with Storybook MDX" explains the details.

Logic suggests that such examples can be formed on the fly based on the information obtained directly from the code. No need to list the colors manually: as we usually store the list of them as a JavaScript hash, we can iterate through all of them and display one by one.

import { ColorPalette, ColorItem } from '@storybook/addon-docs/blocks';

import { colors } from 'path/to/tokens.js';

export const Palette = () => {

  return (
    <ColorPalette>
      {colors.map(color => <ColorItem title={color} subtitle="" colors={[color.value]} />)}
    </ColorPalette>
  )
}

In my recent project, we pushed this idea forward and displayed the helper classes that can assign a specific text color, background color, margins, padding, and borders and that are being used for fast prototyping and quick fixes. In the same manner, we iterate through the list of available CSS classes and form the content of the story to show a piece of text wrapped with <p> tag for each of them.

image
import styles from 'path/to/_helper-classes.scss';

export const textColors = () => {

  const textColors = Object.keys(styles).reduce((acc, key) => {
    if (key.startsWith('color--')) {
      acc.push(key)
    }
    return acc;
  }, []);

  const textColorSamples = textColors.map(cl => (
    <div className={styles[cl]}>Text color with .{styles[cl]}</div>
  ));

  return (
    <div>
      {textColorSamples}
    </div>
  );

};

As you can see, the very SCSS file is imported. So, in the stories, you can analyze the code, or if to be precise, the result of its running through Webpack and create the documentation based on reality.

Likewise, not only for foundational elements but also for some components, the stories can be generic. If your system provides the buttons or icons in all the available colors, you can automatically display all of them.

Moving forward, the stories may get interactive. In my recent project, I implemented hundreds of icons, and each of them was available in light, regular, or filled outline, and at the same time in different sizes. Displaying all these combinations at once would not at all be user-friendly. So I went with a little component that imports all of the icons but displays them with the settings defined in filters. Additionally, I was able to add filtering by icon name with only a few lines of code.

image

For the developers, I show the name of a React component to import if they want to display an icon. But when designers saw it, they asked for normal names. There was a very good reason — the stories are embedded to ZeroHeight where both designers and developers document the components from two different perspectives. What looks nice and handy on the built Storybook website didn't seem so attractive there.

Looking for the shape of stories that would be good for both parties, I came to a decision to have two different types. The component-driven approach assumes that as developers we focus on documentation and so organically produce isolated components with all the needed examples and most of the edge-cases considered. But this experience showed that library developers and library users may have different needs. They are often intersected, but in general, we develop the components focusing on the stories of one type but the resultant documentation is built with the stories of another type. For the list of all icons, I coded a second story, also with the filtering functionality but it displayed different names. But since this story was generic, there is no copy-paste, displaying all the icons once again is only a matter of resuing the story as a component and providing a different setting for the names.

image

Furthermore, having a separate story made it possible to change its view without affecting the original story which the design system developers have in front of their eyes when coding. In that project, the "documenting" stories got a background, stroke, and rounded corners, and such a view fit well for the place where they later were embedded. Coded as a decorator, this view can be applied with a single line of code. Later on, all the stories that were meant to be documentation were placed into the files with a special .docs.stories.js extension, and the decorator is now automatically applied to all of them. Thanks to re-exporting, if such stories are the same as the regular onces, still no copy-paste.

For the production build, there is two options how to deal with different kinds of stories. You may choose to have two different Storybook setups onto the same component codebase. One will be for the inner use while another plays the role of your library vitrine. Or, another option, when developing you may have all kinds of stories rendered but for the library release, you build a Storybook website that skips the unbrushed examples so needed to focus on in the coding phase.