DEV Community

Alex Spinov
Alex Spinov

Posted on

Storybook Has a Free API That Turns Component Development Into a Design System

Storybook is the UI workshop for building components in isolation. Its API lets you document, test, and showcase every component state.

Write a Story

import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  title: "Components/Button",
  component: Button,
  tags: ["autodocs"],
  argTypes: {
    variant: { control: "select", options: ["primary", "secondary", "danger"] },
    size: { control: "radio", options: ["sm", "md", "lg"] },
    disabled: { control: "boolean" },
    onClick: { action: "clicked" },
  },
};

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

export const Primary: Story = {
  args: { variant: "primary", children: "Click me", size: "md" },
};

export const Secondary: Story = {
  args: { variant: "secondary", children: "Cancel" },
};

export const Disabled: Story = {
  args: { variant: "primary", children: "Disabled", disabled: true },
};

export const AllVariants: Story = {
  render: () => (
    <div style={{ display: "flex", gap: 8 }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
    </div>
  ),
};
Enter fullscreen mode Exit fullscreen mode

Interaction Tests

import { within, userEvent, expect } from "@storybook/test";

export const WithInteraction: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole("button");

    await expect(button).toBeEnabled();
    await userEvent.click(button);
    await expect(canvas.getByText("Clicked!")).toBeInTheDocument();
  },
};

export const FormTest: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText("Email"), "test@example.com");
    await userEvent.type(canvas.getByLabelText("Password"), "password123");
    await userEvent.click(canvas.getByRole("button", { name: "Submit" }));

    await expect(canvas.getByText("Success!")).toBeInTheDocument();
  },
};
Enter fullscreen mode Exit fullscreen mode

Decorators: Wrap Components

const meta: Meta<typeof Card> = {
  component: Card,
  decorators: [
    (Story) => (
      <div style={{ padding: "3rem", background: "#f5f5f5" }}>
        <Story />
      </div>
    ),
    // Theme provider
    (Story) => (
      <ThemeProvider theme={darkTheme}>
        <Story />
      </ThemeProvider>
    ),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Loaders: Async Data

export const WithData: Story = {
  loaders: [
    async () => ({
      products: await fetch("/api/products").then(r => r.json()),
    }),
  ],
  render: (args, { loaded: { products } }) => (
    <ProductGrid products={products} />
  ),
};
Enter fullscreen mode Exit fullscreen mode

Visual Testing: Chromatic

npx chromatic --project-token=YOUR_TOKEN
# Captures screenshots of every story
# Detects visual regressions automatically
Enter fullscreen mode Exit fullscreen mode

Document your data components? My Apify tools provide realistic test data for Storybook.

Custom component library? Email spinov001@gmail.com

Top comments (0)