Skip to content
Back to skills

Storybook Setup

ASecurity

Set up Storybook 8 for Next.js 15 or Vite + React — component dev environment, story scaffolding, shadcn/ui integration, dark mode toggle, accessibility checks (a11y addon), and visual regression snapshot configuration. Use when setting up a component library or design system workflow.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 26, 2026
designtypescriptbashreactnextjstestinggitapidocumentation

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 26, 2026

npx -y skills add JoshuaShepherd/my-skills --skill storybook-setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Storybook Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Storybook Setup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/joshuashepherd-storybook-setup/badge)](https://www.skillsdirectory.com/skills/joshuashepherd-storybook-setup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: storybook-setup
description: "Set up Storybook 8 for Next.js 15 or Vite + React — component dev environment, story scaffolding, shadcn/ui integration, dark mode toggle, accessibility checks (a11y addon), and visual regression snapshot configuration. Use when setting up a component library or design system workflow."
user-invocable: true
allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Agent
---

Set up Storybook: $ARGUMENTS

$ARGUMENTS can include:
- Framework hint: "nextjs" or "vite" (auto-detected)
- "with-a11y" — include accessibility addon (default: included)
- "with-chromatic" — include Chromatic visual regression testing
- "minimal" — bare Storybook without addons
- Empty — full setup with a11y addon

---

## Before Starting

1. Read `package.json` to detect framework (Next.js vs Vite)
2. Read `src/app/globals.css` — need to import this in Storybook for Tailwind + tokens
3. Read `tailwind.config.ts` — need to configure content paths
4. Check if `.storybook/` already exists
5. Read `src/components/ui/button.tsx` as example of existing component patterns

---

## Architecture

```
.storybook/
  main.ts              ← Framework config, addons, webpack/vite overrides
  preview.ts           ← Global decorators, Tailwind CSS import, dark mode

src/components/
  ui/
    button.stories.tsx ← Example story for existing shadcn component
  [domain]/
    MyComponent.stories.tsx

stories/               ← (optional) design system documentation stories
  Introduction.mdx
  Colors.stories.tsx
  Typography.stories.tsx
```

---

## Step 1 — Install Storybook

### Next.js
```bash
pnpm dlx storybook@latest init --type nextjs --no-dev
# or manually:
pnpm add -D @storybook/nextjs @storybook/addon-essentials @storybook/addon-interactions @storybook/addon-a11y storybook
```

### Vite + React
```bash
pnpm add -D @storybook/react-vite @storybook/addon-essentials @storybook/addon-interactions @storybook/addon-a11y storybook
```

---

## Step 2 — .storybook/main.ts (Next.js)

```typescript
import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
  stories: [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],

  addons: [
    "@storybook/addon-essentials",      // controls, actions, docs, viewport
    "@storybook/addon-interactions",    // play() function testing
    "@storybook/addon-a11y",            // accessibility checks
    // "@chromatic-com/storybook",      // uncomment if with-chromatic
  ],

  framework: {
    name: "@storybook/nextjs",
    options: {},
  },

  docs: { autodocs: "tag" },

  staticDirs: ["../public"],
};

export default config;
```

---

## Step 3 — .storybook/main.ts (Vite)

```typescript
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],

  addons: [
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
    "@storybook/addon-a11y",
  ],

  framework: {
    name: "@storybook/react-vite",
    options: {},
  },

  docs: { autodocs: "tag" },
};

export default config;
```

---

## Step 4 — .storybook/preview.ts

```typescript
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";   // Import Tailwind + CSS variables

// Mock Next.js navigation for stories
import { initialize, mswLoader } from "msw-storybook-addon"; // optional

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    backgrounds: {
      default: "light",
      values: [
        { name: "light", value: "#ffffff" },
        { name: "dark", value: "#09090b" },  // Match globals.css --background
      ],
    },
    layout: "centered",
  },

  decorators: [
    (Story, context) => {
      // Apply dark mode class when background is dark
      const isDark = context.globals.backgrounds?.value === "#09090b";
      return (
        <div className={isDark ? "dark" : ""}>
          <Story />
        </div>
      );
    },
  ],

  globalTypes: {
    darkMode: {
      description: "Toggle dark mode",
      defaultValue: "light",
      toolbar: {
        title: "Theme",
        icon: "circlehollow",
        items: ["light", "dark"],
        dynamicTitle: true,
      },
    },
  },
};

export default preview;
```

---

## Step 5 — Example Story (Button)

Create `src/components/ui/button.stories.tsx`:

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

const meta: Meta<typeof Button> = {
  title: "UI/Button",
  component: Button,
  tags: ["autodocs"],
  parameters: {
    layout: "centered",
  },
  argTypes: {
    variant: {
      control: "select",
      options: ["default", "destructive", "outline", "secondary", "ghost", "link"],
    },
    size: {
      control: "select",
      options: ["default", "sm", "lg", "icon"],
    },
    disabled: { control: "boolean" },
  },
};

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

export const Default: Story = {
  args: {
    children: "Button",
    variant: "default",
    size: "default",
  },
};

export const Destructive: Story = {
  args: {
    children: "Delete",
    variant: "destructive",
  },
};

export const AllVariants: Story = {
  render: () => (
    <div className="flex flex-wrap gap-4">
      {(["default", "secondary", "destructive", "outline", "ghost", "link"] as const).map(
        (variant) => (
          <Button key={variant} variant={variant}>
            {variant}
          </Button>
        )
      )}
    </div>
  ),
};

export const Loading: Story = {
  args: {
    children: "Loading...",
    disabled: true,
  },
};
```

---

## Step 6 — Story Scaffold for Domain Components

Template for any new component `MyComponent`:

```typescript
// src/components/[domain]/MyComponent.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { MyComponent } from "./MyComponent";

const meta: Meta<typeof MyComponent> = {
  title: "[Domain]/MyComponent",   // e.g. "Cards/CourseCard"
  component: MyComponent,
  tags: ["autodocs"],
  parameters: {
    layout: "padded",              // "centered" | "padded" | "fullscreen"
  },
};

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

export const Default: Story = {
  args: {
    // Provide sensible default props
  },
};

export const WithLongContent: Story = {
  args: {
    // Edge case: long text
  },
};
```

---

## Step 7 — Package.json Scripts

Add to `package.json`:

```json
{
  "scripts": {
    "storybook": "storybook dev -p 6006",
    "storybook:build": "storybook build",
    "storybook:test": "test-storybook"
  }
}
```

---

## Step 8 — .storybook/tsconfig.json (if needed)

If TypeScript path aliases (`@/`) don't resolve in Storybook:

```json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "paths": {
      "@/*": ["../src/*"]
    }
  }
}
```

And in `main.ts`:
```typescript
import path from "path";

const config: StorybookConfig = {
  // ...
  webpackFinal: async (config) => {
    config.resolve!.alias = {
      ...config.resolve!.alias,
      "@": path.resolve(__dirname, "../src"),
    };
    return config;
  },
};
```

For Vite: add alias in `main.ts` via `viteFinal`.

---

## Step 9 — Chromatic (with-chromatic flag)

```bash
pnpm add -D chromatic @chromatic-com/storybook
```

Add to CI workflow (`.github/workflows/ci.yml`):

```yaml
- name: Publish to Chromatic
  uses: chromaui/action@latest
  with:
    projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
    buildScriptName: storybook:build
```

---

## Verify

1. `pnpm storybook` — opens at `http://localhost:6006`
2. Button story renders with all variants
3. Controls panel works — change variant, see live update
4. Dark mode toggle works — component matches dark theme
5. A11y addon shows no violations on Button stories
6. `pnpm storybook:build` — builds static export without errors

---

## Anti-Patterns

- NEVER import from `src/app/` in stories — avoid Next.js app router internals
- NEVER use real API calls in stories — mock with `msw-storybook-addon` or static data
- NEVER skip `tags: ["autodocs"]` on reusable components — auto-generates documentation
- NEVER store Chromatic tokens in code — use GitHub secrets
- NEVER write stories for pages — stories are for components and design system atoms

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.