Skip to main content
Storybook lets you build, test, and review UI components in isolation, and Chromatic automates visual review of UI changes on every PR.
Storybook is not preinstalled in apps/web. It’s an opt-in addition — set it up with the official guide below, then follow the conventions on this page so stories sit naturally next to your components.

Setup

Storybook is stack-agnostic and works with the Vite-based apps/web out of the box. Initialise it from the web app:
cd apps/web
npx storybook@latest init
For visual review, follow the Chromatic GitHub Actions guide.

Conventions

Keep stories where Ship keeps components:
  • shadcn/ui primitivessrc/components/ui/ (e.g. button.tsx, input.tsx, card.tsx)
  • Shared app componentssrc/components/
Point Storybook at both directories and label them so the sidebar mirrors the codebase:
.storybook/main.ts
stories: [
  {
    directory: '../src/components',
    titlePrefix: 'Application Components',
  },
  {
    directory: '../src/components/ui',
    titlePrefix: 'UI Kit',
  },
],

Example: a Button story

Colocate the story with the primitive and import it through the @/ alias. Mirror the component’s real variants — Ship’s Button exposes default, destructive, outline, secondary, ghost, and link, with default, sm, lg, and icon sizes:
src/components/ui/button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';

import { Button } from '@/components/ui/button';

const meta = {
  component: Button,
  args: {
    children: 'Button',
    variant: 'default',
    size: 'default',
  },
  argTypes: {
    variant: {
      options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
      control: { type: 'radio' },
    },
    size: {
      options: ['default', 'sm', 'lg', 'icon'],
      control: { type: 'radio' },
    },
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof Button>;

export const Basic: Story = {};
Stories render in isolation, so they pick up your Tailwind theme tokens only if the global stylesheet is loaded. Import src/globals.css in .storybook/preview.ts to get the same bg-background / text-foreground tokens you see in the app.