DEV Community

Eduardo Henrique Gris
Eduardo Henrique Gris

Posted on

React and typescript components lib, part 5: documentation of components using storybook

Introduction

Portuguese version: Biblioteca de componentes React e typescript, parte 5: documentação de componentes com storybook

In part five, the documentation of the components using storybook will be added, with the aim of demonstrating how the components work and the different possible customizations for them. The idea is to show the practical part of adding storybook in this article and how to perform the documentation. For a reference on how the library works, here is the article I previously wrote as a reference: Documentation of components in React with TypeScript using Storybook (version 8).

Setup

First, the setup will be done with the initial storybook configurations by running the command in the terminal: npx storybook init.
This command will detect the project dependencies and present three steps:

  • indicate which version of storybook will be added
  • ask if the project uses webpack or vite
  • ask if storybook will be used for documentation or testing

Regarding the first point, just proceed. For the second point, the project uses rollup, so in practice, neither option applies directly, but since storybook has no direct integration with rollup, the vite option will be chosen, which will be used to run storybook. For the last question, specify that storybook will be used for documentation.
After finishing the process, libraries that allow running storybook will be automatically added.
At the time of writing this article, it generated the following versions:

"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-onboarding": "^8.6.12",
"@storybook/blocks": "^8.6.12",
"@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.6.12",
"@storybook/test": "^8.6.12",
"eslint-plugin-storybook": "^0.12.0",
"storybook": "^8.6.12",
Enter fullscreen mode Exit fullscreen mode

And in the package.json, two scripts will be automatically added:

"scripts": {
  "storybook": "storybook dev -p 6006",
  "build-storybook": "storybook build"
},
Enter fullscreen mode Exit fullscreen mode

The first script runs storybook locally, and the second builds the storybook.

It will also generate a .storybook folder with two configured files that allow storybook to run: main.ts and preview.ts.
Additionally, a stories folder will be created, which contains some example documentation. Since we won't be using it, this folder will be removed.

With that, storybook is already successfully configured in case the app uses vite. However, since it uses rollup, it will be necessary to add two libraries to allow it to run successfully:

yarn add vite @storybook/builder-vite --dev

At the time of writing this article, the following versions were generated:

"@storybook/builder-vite": "^8.6.12",
"vite": "^6.3.5",
Enter fullscreen mode Exit fullscreen mode

Components modifications

Before continuing with the documentation, the library components will be adapted to define default properties and make adjustments.
Starting with Tag.tsx:

  • Definition of default props values and removal of textWeight (since there is already a property that allows customizable control of the text's font weight: textFontWeight):
// ...
const Tag = ({
  text = "Tag",
  type = "default",
  textColor = "#fff",
  textFontWeight = 600,
  textFontSize,
  textFontFamily,
  backgroundColor,
  format = "semiRounded",
  borderRadius,
  size = "medium",
  padding,
}: TagProps) => {
Enter fullscreen mode Exit fullscreen mode
  • Adjustment of the component's type definitions, since textWeight is no longer a props in the component, and the default format is now semiRounded:
// ...
export interface TagProps {
  type?: "default" | "success" | "alert" | "error";
  text: string;
  textColor?: string;
  textFontWeight?: number;
  textFontSize?: string;
  textFontFamily?: string;
  backgroundColor?: string;
  format?: "square" | "semiRounded" | "rounded";
  borderRadius?: string;
  size?: "small" | "medium" | "large";
  padding?: string;
}

export interface StyledTagProps {
  $type?: "default" | "success" | "alert" | "error";
  $textColor?: string;
  $textFontWeight?: number;
  $textFontSize?: string;
  $textFontFamily?: string;
  $backgroundColor?: string;
  $format?: "square" | "semiRounded" | "rounded";
  $borderRadius?: string;
  $size?: "small" | "medium" | "large";
  $padding?: string;
}
Enter fullscreen mode Exit fullscreen mode
  • Adjustment in the property definitions using styled-components to more clearly take into account the small size, the default type and the square format:
// ...
export const StyledTag = styled.div<StyledTagProps>`
  border: none;
  padding: ${(props) =>
    props.$padding
      ? props.$padding
      : props.$size === "large"
        ? "15px 20px"
        : props.$size === "medium"
          ? "10px 12px"
          : props.$size === "small" && "7px"};
  background-color: ${(props) =>
    props.$backgroundColor
      ? props.$backgroundColor
      : props.$type === "error"
        ? "#e97451"
        : props.$type === "alert"
          ? "#f8de7e"
          : props.$type === "success"
            ? "#50c878"
            : props.$type === "default" && "#d3d3d3"};
  pointer-events: none;
  border-radius: ${(props) =>
    props.$borderRadius
      ? props.$borderRadius
      : props.$format === "rounded"
        ? "30px"
        : props.$format === "semiRounded"
          ? "5px"
          : props.$format === "square" && "0"};
  width: fit-content;
`;
Enter fullscreen mode Exit fullscreen mode
  • Adjustment in the component's rendering, setting the text font size based on the textFontSize props or the size props:
// ...
}: TagProps) => {
  const fontSize = textFontSize
    ? textFontSize
    : size === "large"
      ? "18px"
      : size === "small"
        ? "14px"
        : "16px";

  return (
    <StyledTag
      data-testid="tag"
      $type={type}
      $backgroundColor={backgroundColor}
      $format={format}
      $borderRadius={borderRadius}
      $size={size}
      $padding={padding}
    >
      <Text
        color={textColor}
        fontWeight={textFontWeight}
        fontSize={fontSize}
        fontFamily={textFontFamily}
      >
        {text}
      </Text>
    </StyledTag>
  );
};
Enter fullscreen mode Exit fullscreen mode

Since the default properties of the Tag component were changed, it will be necessary to update its test file Tag.test.tsx:

  • Adjustment of the expect statements in the first test to validate the default properties:
it("should render component with default properties", () => {
  render(<Tag text="Tag" />);

  const element = screen.getByTestId("tag");

  expect(element).toBeInTheDocument();
  expect(element).toHaveStyleRule("background-color", "#d3d3d3");
  expect(element).toHaveStyleRule("border-radius", "0");
  expect(element).toHaveStyleRule("padding", "7px");    
  expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff");
});
Enter fullscreen mode Exit fullscreen mode
  • Removal of the test should render component with bold text font weight, since the props it relied on was removed.

Now regarding Text.tsx:

  • Definition of default prop values:
// ...
const Text = ({
  children,
  color="#000",
  weight="normal",
  fontWeight,
  fontSize="16px",
  fontFamily,
}: TextProps) => (
Enter fullscreen mode Exit fullscreen mode
  • Adjustment in the property definitions using styled-components, so that in some cases the property is only set if the related props is provided:
// ...
export const StyledText = styled.span<StyledTextProps>`
  ${(props) => props.$color && `color: ${props.$color};`}
  ${(props) => props.$fontSize && `font-size: ${props.$fontSize};`}
  font-weight: ${(props) =>
    props.$fontWeight
      ? props.$fontWeight
      : props.$weight
        ? props.$weight
        : "normal"};
  ${(props) => props.$fontFamily && `font-family: ${props.$fontFamily};`}
`;
Enter fullscreen mode Exit fullscreen mode

Components documentation

For the component documentation, two types of files will be created for each component:

  • stories: where the customization scenarios to be shown in the documentation will be defined

  • mdx: the component documentation, where the texts will be written and the scenarios from the stories file will be embedded

Before moving on to the documentation, a component will be created to centralize the components rendered in the documentation and add a small space between them. A folder named StorybookContainer will be created inside src/components:

  • file StorybookContainer.tsx:
import React from "react";
import styled from "styled-components";

export interface StorybookContainerProps {
  children: React.ReactNode;
}

export const StyledStorybookContainer = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
`;

const StorybookContainer = ({ children }: StorybookContainerProps) => (
  <StyledStorybookContainer>
    {children}
  </StyledStorybookContainer>
);

export default StorybookContainer;
Enter fullscreen mode Exit fullscreen mode
  • file index.ts:
export { default } from "./StorybookContainer";
Enter fullscreen mode Exit fullscreen mode

Now, we can start documenting the Tag component by creating the following file inside the folder src/components/Tag:

  • Tag.stories.tsx
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import Tag from "./Tag";
import StorybookContainer from "../StorybookContainer/StorybookContainer";

const meta: Meta<typeof Tag> = {
  title: "Tag",
  component: Tag,
  argTypes: {},
};

export default meta;

type Story = StoryObj<typeof Tag>;

export const Default: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
    </StorybookContainer>
  ),
};

export const PredefinedType: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} type="success" />
      <Tag {...args} type="alert" />
      <Tag {...args} type="error" />
    </StorybookContainer>
  ),
};

export const PredefinedFormat: Story = {
  args: {
    format: "square",
  },
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} format="semiRounded" />
      <Tag {...args} format="rounded" />
    </StorybookContainer>
  ),
};

export const PredefinedSize: Story = {
  args: {
    size: "small",
  },
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} size="medium" />
      <Tag {...args} size="large" />
    </StorybookContainer>
  ),
};

export const Text: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} text="Example" />
    </StorybookContainer>
  ),
};

export const BackgroundColor: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} backgroundColor="#3b82f6" />
    </StorybookContainer>
  ),
};

export const CustomFormat: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} borderRadius="15px 2px" />
    </StorybookContainer>
  ),
};

export const CustomSize: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} padding="20px 40px" />
    </StorybookContainer>
  ),
};

export const TextColor: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} textColor="#000" />
    </StorybookContainer>
  ),
};

export const TextFontWeight: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} textFontWeight={100} />
    </StorybookContainer>
  ),
};

export const TextFontFamily: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} textFontFamily="Arial" />
    </StorybookContainer>
  ),
};

export const TextFontSize: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Tag {...args} />
      <Tag {...args} textFontSize="20px" />
    </StorybookContainer>
  ),
};
Enter fullscreen mode Exit fullscreen mode

In it, under Meta, the component being documented is defined, along with the title that will appear in storybook’s sidebar menu. Each export const defines a scenario that will be included in the documentation. Citing two example scenarios, the first Default will render the component with its default props. The second PredefinedType will show how the component behaves with variations of the type props.

  • Tag.mdx
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as Stories from "./Tag.stories";

<Meta of={Stories} />

# Tag

Tag base component.

<Canvas of={Stories.Default} withToolbar />

<Controls of={Stories.Default} />

## Predefined properties

### Type

There are four type predefined properties: default, success, alert and error.

<Canvas of={Stories.PredefinedType} withToolbar />

### Format

There are three format predefined properties: square, semiRounded (default) and rounded.

<Canvas of={Stories.PredefinedFormat} withToolbar />

### Size

There are three size predefined properties: small, medium (default) and large.

<Canvas of={Stories.PredefinedSize} withToolbar />

## Custom properties

### Text

Tag text can be modified.

<Canvas of={Stories.Text} withToolbar />

### Background Color

Tag background color can be modified.

<Canvas of={Stories.BackgroundColor} withToolbar />

### Format

Tag format can be modified by border radius definition.

<Canvas of={Stories.CustomFormat} withToolbar />

### Size

Tag size can be modified by padding definition.

<Canvas of={Stories.CustomSize} withToolbar />

### Text Color

Tag text color can be modified.

<Canvas of={Stories.TextColor} withToolbar />

### Text Font Weight

Tag text font weight can be modified.

<Canvas of={Stories.TextFontWeight} withToolbar />

### Text Font Family

Tag text font family can be modified.

<Canvas of={Stories.TextFontFamily} withToolbar />

### Text Font Size

Tag text font size can be modified.

<Canvas of={Stories.TextFontSize} withToolbar />
Enter fullscreen mode Exit fullscreen mode

In the file above, <Canvas of={Stories.Default} withToolbar /> renders the component using its Default scenario. Just below, <Controls of={Stories.Default} /> displays a table with all the component’s props available for customization. By adjusting these props, it's possible to see in real time how they impact the component above the table.
In Predefined properties, scenarios are shown where, based on modified props, the app itself defines the associated css.
In Custom properties, scenarios are shown where the modified props directly affect the defined css.

Running storybook locally with yarn storybook:

Image description

Now, the documentation for the Text component will be created, by adding the following file inside the folder src/components/Text:

  • Text.stories.tsx
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import Text from "./Text";
import StorybookContainer from "../StorybookContainer/StorybookContainer";

const meta: Meta<typeof Text> = {
  title: "Text",
  component: Text,
  argTypes: {},
};

export default meta;

type Story = StoryObj<typeof Text>;

export const Default: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
    </StorybookContainer>
  ),
};

export const PredefinedFontWeight: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
      <Text {...args} weight="bold">
        Text
      </Text>
    </StorybookContainer>
  ),
};

export const Color: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
      <Text {...args} color="#800080">
        Text
      </Text>
    </StorybookContainer>
  ),
};

export const CustomFontWeight: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
      <Text {...args} fontWeight={900}>
        Text
      </Text>
    </StorybookContainer>
  ),
};

export const FontSize: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
      <Text {...args} fontSize="30px">
        Text
      </Text>
    </StorybookContainer>
  ),
};

export const FontFamily: Story = {
  args: {},
  render: (args) => (
    <StorybookContainer>
      <Text {...args}>Text</Text>
      <Text {...args} fontFamily="Arial">
        Text
      </Text>
    </StorybookContainer>
  ),
};
Enter fullscreen mode Exit fullscreen mode
  • Text.mdx
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as Stories from "./Text.stories";

<Meta of={Stories} />

# Text

Text base component.

<Canvas of={Stories.Default} withToolbar />

<Controls of={Stories.Default} />

## Predefined properties

### Font Weight

There are two font weight predefined properties: normal(default) and bold.

<Canvas of={Stories.PredefinedFontWeight} withToolbar />

## Custom properties

### Color

Text color can be modified.

<Canvas of={Stories.Color} withToolbar />

### Font Weight

Text font weight can be modified.

<Canvas of={Stories.CustomFontWeight} withToolbar />

### Font Size

Text font size can be modified.

<Canvas of={Stories.FontSize} withToolbar />

### Font Family

Text font family can be modified.

<Canvas of={Stories.FontFamily} withToolbar />
Enter fullscreen mode Exit fullscreen mode

It will look as follows:

Image description

Deploy storybook

Now we have both components documented and storybook running successfully locally. But the goal is not for the documentation to be used only by those developing the library - it should be available to showcase the components the library offers to those considering adding it. To achieve this, chromatic will be used to deploy the documentation and make it accessible, that is a publishing service made by the storybook maintainers. First, the library will be added:

yarn add chromatic --dev

At the time of writing this article, it generated the following version:

"chromatic": "^12.0.0",
Enter fullscreen mode Exit fullscreen mode

Now, follow these steps to deploy:

  • Go to the sign-up page
  • Select Connect with GitHub (assuming your repository is hosted there)
  • After signing up, choose Choose GitHub repo
  • Select the repository for your library from your github account.
  • Copy and save the command shown under Publish your Storybook. At this point, you can’t proceed without running this command in the terminal, but before that, let's build storybook
  • In the terminal, run yarn build-storybook
  • Then, in the terminal, run the command provided by chromatic (every time you need to deploy, this exact command must be executed after building)

Once the process completes successfully, the terminal will confirm that storybook was published and will display a url under View your Storybook at "url".
By visiting this url, you’ll be able to see the component documentation.

README.md

Now that storybook has been published, the url where it is hosted will be added to the app's README (I’ll include my generated URL here, in your case, you should insert yours in that spot):

Image description

.gitignore file

In the .gitignore file, the storybook-static folder will be added to be ignored, since it is generated when running yarn build-storybook:

dist
node_modules

*storybook.log
storybook-static
Enter fullscreen mode Exit fullscreen mode

package.json

The version inside package.json will be changed to 0.5.0, since a new version of the library will be released:

{
  "name": "react-example-lib",
  "version": "0.5.0",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/griseduardo/react-example-lib.git"
  },
  "scripts": {
    "build": "rollup -c --bundleConfigAsCjs",
    "lint-src": "eslint src",
    "lint-src-fix": "eslint src --fix",
    "lint-fix": "eslint --fix",
    "format-src": "prettier src --check",
    "format-src-fix": "prettier src --write",
    "format-fix": "prettier --write",
    "test": "jest",
    "prepare": "husky",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "lint-staged": {
    "src/components/**/*.{ts,tsx}": [
      "yarn lint-fix",
      "yarn format-fix"
    ],
    "src/components/**/*.tsx": "yarn test --findRelatedTests --bail"
  },
  "devDependencies": {
    "@babel/core": "^7.26.10",
    "@babel/preset-env": "^7.26.9",
    "@babel/preset-react": "^7.26.3",
    "@babel/preset-typescript": "^7.26.0",
    "@eslint/js": "^9.19.0",
    "@rollup/plugin-commonjs": "^28.0.2",
    "@rollup/plugin-node-resolve": "^16.0.0",
    "@rollup/plugin-terser": "^0.4.4",
    "@rollup/plugin-typescript": "11.1.6",
    "@storybook/addon-essentials": "^8.6.12",
    "@storybook/addon-interactions": "^8.6.12",
    "@storybook/addon-onboarding": "^8.6.12",
    "@storybook/blocks": "^8.6.12",
    "@storybook/builder-vite": "^8.6.12",
    "@storybook/react": "^8.6.12",
    "@storybook/react-vite": "^8.6.12",
    "@storybook/test": "^8.6.12",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
    "@types/jest": "^29.5.14",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "chromatic": "^12.0.0",
    "eslint": "^9.19.0",
    "eslint-plugin-react": "^7.37.4",
    "eslint-plugin-storybook": "^0.12.0",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-styled-components": "^7.2.0",
    "lint-staged": "^15.5.0",
    "prettier": "^3.4.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "rollup": "^4.30.1",
    "rollup-plugin-dts": "^6.1.1",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "storybook": "^8.6.12",
    "styled-components": "^6.1.14",
    "typescript": "^5.7.3",
    "typescript-eslint": "^8.23.0",
    "vite": "^6.3.5"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "styled-components": "^6.1.14"
  },
  "eslintConfig": {
    "extends": [
      "plugin:storybook/recommended"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

CHANGELOG file

Since a new version will be released, the CHANGELOG.md will be updated with what has been changed:

## 0.5.0

_May. 29, 2025_

- change Tag and Text default behavior
- add storybook
- add Tag and Text storybook docs

## 0.4.0

_Abr. 29, 2025_

- setup husky and lint-staged
- define pre-commit actions

## 0.3.0

_Mar. 24, 2025_

- setup jest and testing-library
- add components tests

## 0.2.0

_Fev. 24, 2025_

- setup typescript-eslint and prettier
- add custom rules

## 0.1.0

_Jan. 29, 2025_

- initial config
Enter fullscreen mode Exit fullscreen mode

Folders structure

The folder structure will be as follows:

Image description

Publication of a new version

The first step is to check if the rollup build runs successfully. To do this, run yarn build in the terminal, as defined in package.json. If the build is successful, proceed to publish the new version of the library with: npm publish --access public

Conclusion

The purpose of this article was to set up storybook, create documentation for the components in the app and deploy it, since documentation is important to showcase the components the library provides. Here is the repository on github and the library on npmjs with the new modifications.

Top comments (0)