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
orvite
- 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",
And in the package.json
, two scripts will be automatically added:
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
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",
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) => {
- Adjustment of the component's type definitions, since
textWeight
is no longer a props in the component, and the defaultformat
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;
}
- Adjustment in the property definitions using styled-components to more clearly take into account the small
size
, the defaulttype
and the squareformat
:
// ...
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;
`;
- Adjustment in the component's rendering, setting the text font size based on the
textFontSize
props or thesize
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>
);
};
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");
});
- 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) => (
- 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};`}
`;
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;
- file
index.ts
:
export { default } from "./StorybookContainer";
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>
),
};
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 />
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
:
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>
),
};
- 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 />
It will look as follows:
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",
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):
.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
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"
]
}
}
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
Folders structure
The folder structure will be as follows:
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)