Storybook
Interactive component workshop for Sitka. Browse every component in isolation, switch themes, test viewports, and check accessibility — all without running the full app.
Overview
Sitka ships with Storybook 8 pre-configured. All 31 components have stories in src/stories/. The setup uses @storybook/react-vite so Storybook builds independently of Next.js — no version coupling, instant HMR.
Storybook
8.6
Framework
react-vite
Tailwind
v4 via Vite
Stories
31 components
Run locally
Start Storybook on localhost:6006:
npm run storybookTo build a static version for deployment:
npm run build-storybook
# output → storybook-static/Built-in features
Theme switcher
Toggle between dark and light via the toolbar. Uses data-theme attribute — identical to the production implementation.
Viewport presets
Mobile (390 × 844), Tablet (768 × 1024), Desktop (1440 × 900) — match real device breakpoints.
Accessibility audit
The a11y addon runs axe-core on every story automatically. Violations surface in the Accessibility panel.
Controls & autodocs
All props are wired to interactive controls. Component pages are auto-generated from TypeScript types and JSDoc.
Writing a story
Stories live in src/stories/ and follow the Component Story Format (CSF 3). Here is the Button story as a reference:
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "@/components/ui/Button";
const meta: Meta<typeof Button> = {
title: "Actions/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: { control: "select", options: ["primary", "secondary", "ghost", "danger"] },
size: { control: "select", options: ["sm", "md", "lg"] },
disabled: { control: "boolean" },
loading: { control: "boolean" },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: { variant: "primary", size: "md", children: "Button" },
};
export const Secondary: Story = {
args: { variant: "secondary", size: "md", children: "Button" },
};Configuration
The config lives in .storybook/.
.storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
import path from "path";
import tailwindVite from "@tailwindcss/vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx)"],
addons: ["@storybook/addon-essentials", "@storybook/addon-a11y"],
framework: { name: "@storybook/react-vite", options: {} },
staticDirs: ["../public"],
async viteFinal(cfg) {
const { mergeConfig } = await import("vite");
return mergeConfig(cfg, {
plugins: [tailwindVite()],
resolve: { alias: { "@": path.resolve(__dirname, "../src") } },
});
},
};
export default config;.storybook/preview.tsx
import type { Preview, Decorator } from "@storybook/react";
import React from "react";
import "../src/app/globals.css";
const withTheme: Decorator = (Story, context) => {
const theme = context.globals.theme ?? "dark";
return (
<div
data-theme={theme}
style={{
background: theme === "dark" ? "rgb(9 9 12)" : "rgb(252 252 253)",
minHeight: "100vh",
padding: "2rem",
colorScheme: theme,
}}
>
<Story />
</div>
);
};
export default {
decorators: [withTheme],
globalTypes: {
theme: {
name: "Theme",
defaultValue: "dark",
toolbar: {
icon: "circlehollow",
items: [
{ value: "dark", title: "Dark", icon: "moon" },
{ value: "light", title: "Light", icon: "sun" },
],
showName: true,
},
},
},
} satisfies Preview;Related
- Component docs — props, variants, and usage guidelines for every component.
- Accessibility — the a11y standards each component is tested against.
- React · Next.js — how to integrate Sitka into a Next.js project.