Kitchen Sink design system workflow for Next.js and React projects, with secondary support for Astro, SvelteKit, Nuxt, and static HTML. Use when asked for a Kitchen Sink page, Design System, UI Audit, Style Guide, or Component Inventory, or when a project needs a component inventory plus component creation and a sink page implementation. Covers CVA variant architecture, Tailwind v3/v4 token systems, shadcn/ui integration, and TinaCMS content modeling.
Build every component for real, wire it into a single sink page, and let the page prove the design system works.
Before anything else, identify the project's framework and determine whether a design system already exists.
| Signal file / directory | Framework |
|---|---|
next.config.*, app/ or pages/ | Next.js (React) |
astro.config.*, src/components/ | Astro |
nuxt.config.* | Nuxt (Vue) |
svelte.config.* | SvelteKit |
| None of the above | Static HTML/CSS |
Once detected, lock in these mappings for the rest of the workflow:
| Concept | React / Next.js | Astro | Static HTML |
|---|---|---|---|
| Component | .tsx in components/ | .astro in src/components/ | Reusable HTML snippet |
| Component call | <Button variant="primary" /> | <Button variant="primary" /> | Copy/paste or include |
| Props / params | React props (TypeScript) | Astro props (TypeScript) | CSS classes / data-attrs |
| Interactivity | useState, event handlers | client:load + framework islands | Vanilla JS or Alpine.js |
| Sink route | app/sink/page.tsx | src/pages/sink.astro | sink.html |
| Prod guard | process.env check -> return null | import.meta.env check | Don't deploy the file |
| Content authors | Components in MDX | Components in MDX / .astro | N/A |
| Utility helper | cn() via clsx + tailwind-merge | cn() or class:list | Inline concat |
After identifying the framework, also detect:
tina/), Sanity, Contentful, plain markdown, none.cn()?Detect which Tailwind version is in use, then read the appropriate source:
tailwind.config.js / tailwind.config.ts for theme.extend (custom colors, spacing, fonts, breakpoints).globals.css, app.css, or the project's main CSS entry for @theme blocks and CSS custom properties (--color-*, --spacing-*, --font-*). Also check for @import "tailwindcss" as a v4 indicator.tailwind.config.* exists -> v3. If the CSS entry contains @theme or @import "tailwindcss" -> v4./* app/globals.css -- Tailwind v4 */
@import "tailwindcss";
@theme {
--color-primary: oklch(0.45 0.15 260);
--color-primary-foreground: oklch(0.98 0.01 260);
--color-secondary: oklch(0.92 0.02 260);
--color-secondary-foreground: oklch(0.2 0.05 260);
--color-destructive: oklch(0.55 0.2 25);
--color-muted: oklch(0.95 0.01 260);
--color-muted-foreground: oklch(0.55 0.02 260);
--color-accent: oklch(0.93 0.02 260);
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
}
In v4, components reference these tokens as utility classes directly: bg-primary, text-muted-foreground, rounded-lg. No theme.extend config needed.
Scan the project root (and common subdirectories like docs/, .github/, .cursor/, .agent/) for any of these files:
Brand & style guides:
GEMINI.md, CLAUDE.md, AGENTS.md, COPILOT.md.cursorrules, .cursor/rules.github/copilot-instructions.mdbrand-guide.md, STYLE.md, BRAND.mddesign-tokens.json, tokens.css, tokens.jsonCONTENT_GUIDELINES.md, VOICE.md, BRAND_VOICE.mdAgent configuration files:
.clinerules, .windsurfrules.agent/skills/*/SKILL.mdREADME.md (check for design system or brand sections)If any of these contain design direction (colors, typography, voice, component patterns), enter Adopt mode. Otherwise, enter Establish mode.
When the project already has documented design direction:
--blue-500) or semantic token (purpose: --color-interactive)w-[37px]) instead of design scaleWhen the project has no documented design system:
globals.css, tailwind.config.*, component filesdesign-tokens.md with:
Automated option: Run bash scripts/scan-components.sh [component_dir] from the skill directory to get a Phase 0 discovery report + EXISTING / MISSING inventory against the tiered checklist.
Reference: design-system-discovery.md
Compare existing components against the tiered checklist. Mark each:
Include when the site has a CMS or content authors who write MDX:
For React/Astro, the component serves both sink and content-author roles via MDX.
Every component -- whether EXISTING or newly created -- must follow the base + variant pattern. This makes components predictable for both humans and AI agents.
Use class-variance-authority (CVA) or an equivalent pattern to separate structural base classes from variant-specific classes:
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; // clsx + tailwind-merge wrapper
// -- Base + Variants --------------------------------------------------------
const buttonVariants = cva(
// Base: shared structure (always applied)
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
outline: "border border-input bg-transparent hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
},
},
compoundVariants: [
{ variant: "destructive", size: "lg", class: "font-semibold" },
],
defaultVariants: {
variant: "primary",
size: "md",
},
}
);
// -- Component --------------------------------------------------------------
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button className={cn(buttonVariants({ variant, size }), className)} {...props} />
);
}
Astro components define a TypeScript Props interface in frontmatter with defaults via destructuring. Build classes from variant/size, use class:list for merging. Pattern mirrors React CVA but uses Astro's native Props + <slot />.
Use BEM class conventions (.btn, .btn--primary, .btn--sm) and document the contract in an HTML comment at the top of the file listing available classes and data attributes.
VariantProps<>, Astro: Props interface) so consumers (including AI agents) can discover available variants.isDestructive ? "bg-red-500" : "bg-blue-500" inline. All visual branching goes through the variant API.Components should reference semantic token names, not primitive color names:
| Primitive (avoid) | Semantic (use) |
|---|---|
bg-blue-500 | bg-primary |
text-gray-500 | text-muted-foreground |
border-red-500 | border-destructive |
bg-gray-100 | bg-muted |
The semantic layer means dark mode, theme changes, and brand pivots only require updating the token definitions -- component code stays unchanged.
If the project lacks a class-merging utility: React -- add cn() via clsx + tailwind-merge. Astro -- use built-in class:list. Static -- inline concat or a tiny JS helper.
Every design system needs content guidance and a visual identity.
The sink page includes a Voice & Tone section covering:
Reference: voice-and-tone.md -- full templates, examples, and writing checklist.
Photography sourced from the web cannot be used directly (copyright, brand inconsistency). Define an illustration style and a reinterpretation pipeline to transform reference photos into brand-safe assets.
Reference: image-reinterpretation.md -- full pipeline, prompt templates, validation checklist, and sink page integration code.
Animation is the body language of the product. Define motion patterns in the sink for consistent, purposeful animation.
prefers-reduced-motion.--duration-instant (100ms), --duration-fast (200ms), --duration-normal (300ms), --duration-slow (500ms).--ease-out for entrances, --ease-in for exits, --ease-in-out for state changes.prefers-reduced-motion media query or use Tailwind motion-safe:/motion-reduce: modifiers.Reference: motion-guidelines.md -- full CSS/Tailwind code, keyframe definitions, Framer Motion patterns, and reduced-motion implementation.
For every MISSING item from Phase 1, create the component using the framework's native patterns.
| Framework | Location | Convention |
|---|---|---|
| React / Next.js | components/ or components/ui/ | Button.tsx, card.tsx -- match existing project convention |
| Astro | src/components/ | Button.astro, Card.astro |
| Static HTML | components/ or includes/ | button.html, card.html |
Props interface in frontmatter.useState, event handlersclient:load + framework islands<details>/<summary>When a component is marked as an MDX CANDIDATE in Phase 1:
Create the sink route file based on the framework detected in Phase 0.
React / Next.js -- app/sink/page.tsx
"use client" directive.null when process.env.NEXT_PUBLIC_VERCEL_ENV === "production".examples/minimal-sink.tsx as a starter template.Astro -- src/pages/sink.astro
import.meta.env.PROD and return redirect or empty page.SvelteKit -- src/routes/sink/+page.svelte
$app/environment to check for production.Nuxt -- pages/sink.vue
useRuntimeConfig() to check environment.Static HTML -- sink.html
The sink page follows the same section structure regardless of framework. Adapt the syntax to match.
Sections (in order):
resolveConfig (v3) or read CSS custom properties (v4).iframe containers with fixed widths (320px, 768px) to verify mobile layouts.Run these checks before considering the sink complete.
# Build
pnpm build
# Lint
pnpm lint
# Accessibility audit
pnpm dlx @axe-core/cli http://localhost:3000/sink
For continuous verification, add to your CI pipeline:
@axe-core/cli or playwright-axe against /sink and fail on violations.# Example Playwright visual regression test
pnpm dlx playwright test sink.spec.ts --project=chromium
prefers-reduced-motion respected.The sink page is never CMS-managed -- it's a developer tool.
However, when a CMS is present, content-author-facing components (Tier 3) should respect the CMS content model:
tina/config.ts collections.Ensure the design system is consumable by AI agents: use semantic tokens over primitives, store tokens in CSS custom properties and/or JSON, export typed props as the API contract, use purpose-based naming (text-muted-foreground not gray-light), reference tokens in the project's rules file (CLAUDE.md, .cursorrules, etc.), and treat the sink page as the single source of truth.
When available, leverage: design-lookup for CSS components, SVG icons, and design inspiration during Establish mode. openai-image for illustration reinterpretation and brand-safe asset generation. deep-research for evaluating design system approaches from scratch. This skill acts as the integrator -- consuming companion skill outputs and codifying them into the component library and sink page.
{/* TODO: add button */} is never acceptable. Build the real component.w-[35px] or text-[#ff0000] instead of using config tokens.isDestructive ? "bg-red-500" : "bg-blue-500" instead of using variants.import X from "../other-page/page" creates coupling and build issues.blue-primary instead of color-interactive. Semantic names survive brand pivots.For detailed per-component specs (expected props, variants, states, accessibility), the sink page section template, and concrete code examples: