| name | storybook-config |
| description | Storybook 10 with the Vite builder for snapmatch — shares the app's Vite, Tailwind, and alias config from a single source (never forked), wires `@storybook/react-vite` in `main.ts`, and registers global decorators in `preview.tsx`. Triggers on: storybook config, storybook vite builder, storybook main.ts, storybook preview.ts, shared vite config, storybook addon. |
| license | MIT |
Sub-skill of storybook. Owns .storybook/main.ts, .storybook/preview.tsx, the viteFinal hook that re-uses the app's Vite config, and the addon registration list. The single load-bearing rule: Storybook's Vite/Tailwind/alias config is the same config the app uses — imported, not duplicated.
When to invoke
- Authoring or editing
apps/example-game/.storybook/main.ts or .storybook/preview.tsx.
- Diagnosing "Tailwind doesn't work in stories," "alias
@/... doesn't resolve in stories," or "the app builds but Storybook errors on the same import."
- Adding a Storybook addon (a11y, docs, themes, the TanStack Start integration).
- Wiring
viteFinal to merge in app plugins or path aliases.
- Setting the story-glob pattern so co-located
*.stories.tsx files are discovered.
Owns
Storybook 10 with the Vite builder, shared Vite + Tailwind + alias config from a single source (never forked), main.ts/preview.tsx, addons.
Defers to
storybook (parent) — version pin and routing.
tailwind — for @tailwindcss/vite itself. Storybook re-uses the app's Vite config, which already has the Tailwind plugin; Storybook never adds its own copy.
tanstack-router-routing — for the routeTree.gen.ts that route-aware stories may need (via storybook-addon-tanstack-start).
react-19-primitives — for <Suspense> boundaries used by global decorators that wrap stories.
idb — for the root idbHydrationPromise that a global decorator may need to feed use(...) so stories see hydrated state.
biome — .storybook/main.ts, .storybook/preview.tsx, and any *.stories.tsx are linted by Biome like any other TS/TSX. Story files are not exempt from bun run check.
bun-package-manager — for installing storybook, @storybook/react-vite, addons.
Snapmatch stack rules
- Pillar 1 (Storybook-first) means: Storybook must boot, render, and lint before any feature lands. A broken
main.ts blocks the whole construction surface.
- Pillar 4 (CLI-gate-first) means:
.storybook/*.ts and *.stories.tsx go through biome ci, tsgo --noEmit, and Playwright story tests in bun run check. Config typos surface in the gate, not just in the IDE.
- Shared Vite config is load-bearing. Storybook imports the app's
vite.config.ts and merges it via viteFinal. Adding @tailwindcss/vite, vite-tsconfig-paths, or tanstackStart() separately in .storybook/main.ts is a bug — it causes drift the first time the app config changes.
- TypeScript-first config:
.storybook/main.ts and .storybook/preview.tsx (never .js).
- ESM only (
"type": "module"); Storybook 10 is ESM-only.
- Node
>=20.19 || >=22.12 for Storybook 10 (the snapmatch pin is Node 25 — fine).
Patterns
.storybook/main.ts — framework, stories glob, addons, viteFinal
import type { StorybookConfig } from "@storybook/react-vite";
import { mergeConfig } from "vite";
import path from "node:path";
const config: StorybookConfig = {
framework: { name: "@storybook/react-vite", options: {} },
stories: ["../app/components/**/*.stories.@(ts|tsx)"],
addons: [
"@storybook/addon-docs",
"@storybook/addon-a11y",
"storybook-addon-tanstack-start",
],
async viteFinal(viteConfig) {
return mergeConfig(viteConfig, {
resolve: {
alias: { "@": path.resolve(__dirname, "../app") },
},
});
},
};
export default config;
The framework block pins @storybook/react-vite. The stories glob walks the components tree (Pillar 1: stories live next to components, not in a parallel __stories__/ tree). The viteFinal hook is where the app's Vite config (Tailwind v4 plugin, React plugin, path aliases) flows into Storybook — never a duplicate copy.
.storybook/preview.tsx — global decorators, parameters, hydration
import type { Preview } from "@storybook/react-vite";
import { Suspense, use } from "react";
import { Provider } from "jotai";
import { idbHydrationPromise } from "../app/state/hydration";
import "../app/styles/index.css";
function HydrateThenRender({ children }: { children: React.ReactNode }) {
use(idbHydrationPromise);
return <>{children}</>;
}
const preview: Preview = {
parameters: {
controls: { matchers: { color: /(background|color)$/i, date: /Date$/ } },
layout: "centered",
},
decorators: [
(Story) => (
<Suspense fallback={<div data-test="story-hydrating">…</div>}>
<HydrateThenRender>
<Provider>
<Story />
</Provider>
</HydrateThenRender>
</Suspense>
),
],
};
export default preview;
The decorator stack mirrors the app shell so a story sees the same world a route does: the IDB hydration promise resolves once, then the Jotai <Provider> wraps the story. Use .tsx (not .ts) because the decorator returns JSX.
Why the shared Vite config is non-negotiable
import tailwindcss from "@tailwindcss/vite";
async viteFinal(viteConfig) {
return mergeConfig(viteConfig, { plugins: [tailwindcss()] });
}
The app already has @tailwindcss/vite in apps/example-game/vite.config.ts (see tailwind). Adding it again in .storybook/main.ts runs the plugin twice on the same CSS, which silently corrupts token extraction and causes "works in app, broken in stories." The fix: import nothing in viteFinal that's already in the app config.
Path aliases — single source via vite-tsconfig-paths
import viteTsConfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [viteTsConfigPaths({ projects: ["./tsconfig.json"] }), ],
});
vite-tsconfig-paths reads the same tsconfig.json paths map that tsgo --noEmit uses (see ts). Storybook re-uses this via viteFinal — one source of truth for both the app and stories.
Story discovery glob
stories: [
"../app/components/**/*.stories.@(ts|tsx)",
];
Co-located, walking the components/ tree only. Do not glob app/routes/** — route-level stories are an anti-pattern (build the inner pieces; the route is a composition site).
Addon registration order
addons: [
"@storybook/addon-docs",
"@storybook/addon-a11y",
"storybook-addon-tanstack-start",
];
Addons load in order. Keep the list minimal — every addon adds boot time and a surface to maintain. Add @storybook/addon-themes only if a theme toggle is shown in the toolbar.
Workspace install
bun add -D \
storybook@^10 \
@storybook/react-vite@^10 \
@storybook/addon-docs@^10 \
@storybook/addon-a11y@^10 \
storybook-addon-tanstack-start
Pinned to Storybook 10 majors; @storybook/react-vite is the only allowed framework. See bun-package-manager.
Scripts
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}
storybook dev is the local construction surface and the URL Playwright story tests target (http://localhost:6006/iframe.html?id=...). storybook build produces storybook-static/ — never commit it; add to .gitignore.
Anti-patterns
- Don't fork the Vite config in
.storybook/main.ts — re-use the app's via viteFinal. Adding @tailwindcss/vite separately is the canonical way to break Tailwind in stories.
- Don't author
.storybook/main.js or .storybook/preview.js — TypeScript only. JS configs lose autocomplete and type checking.
- Don't put
*.stories.tsx in a __stories__/ parallel tree — co-locate next to the component. The glob walks the components tree directly.
- Don't use
@storybook/react, @storybook/react-webpack5, @storybook/nextjs, or @storybook/nextjs-vite — only @storybook/react-vite is correct for this stack.
- Don't import the
Meta/StoryObj types from @storybook/react — use @storybook/react-vite (the framework path). The renderer-only path is wrong here.
- Don't add
webpackFinal — there is no Webpack here. Configuration goes through viteFinal.
- Don't commit
storybook-static/ — .gitignore it. It's a build artifact.
- Don't enable test-runner addons for this repo — Playwright drives stories. See
playwright-story-tests.
- Don't omit the Tailwind CSS import in
preview.tsx — without it, stories render unstyled and the visual debug loop breaks. Import the same app/styles/index.css the app uses.
Triggers on
storybook config, storybook vite builder, storybook main.ts, storybook preview.ts, shared vite config, storybook addon