Guide

How to Vibe Code
a Design System

A practical guide to building a production-grade design system using Claude Code as your design partner. Based on the actual process that produced Arctic Midnight.

This is not theory. This is what we did. Here is how.

Claude CodeNext.jsTailwind v4RemotionMesh GradientsSKILL.md
01Phase

Source of Truth

Everything starts with data, not design. Your brand config is a set of JSON files that describe your visual identity in machine-readable format. Claude reads these at the start of every session.

Step 1

Create brand-config/visual.json

Define your colors, fonts, spacing, grain intensity, and easing curve in a single JSON file. This becomes the source of truth that drives everything downstream.

json
{
  "theme_name": "Arctic Midnight",
  "colors": {
    "background": { "light": "#F9F9F9", "dark": "#0A0A0A" },
    "foreground": { "light": "#0A0A0A", "dark": "#F9F9F9" },
    "primary": { "light": "#7C3AED", "dark": "#8B5CF6" },
    "aurora": [
      { "name": "Malachite", "hex": "#059669" },
      { "name": "Deep Cyan", "hex": "#0891B2" },
      { "name": "Modh Purple", "hex": "#7C3AED" },
      { "name": "Dracula Red", "hex": "#BE123C" },
      { "name": "Amber", "hex": "#D97706" }
    ]
  },
  "grain": { "web": 0.03, "mesh": 0.25, "video": 0.15 },
  "easing": "cubic-bezier(0.23, 1, 0.32, 1)"
}
Tell Claude

Read brand-config/visual.json, then update globals.css to express every color as a CSS custom property. Add utility tokens for sub-pixel borders, hover states, and backdrop effects. Support light and dark modes.

Step 2

Convert to CSS Custom Properties

Express every token as a CSS custom property in globals.css. Wire them into Tailwind via @theme inline. Now bg-background, text-primary all resolve to your tokens.

css
:root {
  --background: #F9F9F9;
  --foreground: #0A0A0A;
  --primary: #7C3AED;
  --aurora-1: #059669;
  --aurora-2: #0891B2;
  --aurora-3: #7C3AED;
  --aurora-4: #BE123C;
  --aurora-5: #D97706;
  --sub-border: rgba(10, 10, 10, 0.05);
  --magnetic-wash: rgba(124, 58, 237, 0.08);
}

.dark {
  --background: #0A0A0A;
  --foreground: #F9F9F9;
  --primary: #8B5CF6;
}

No magic strings. No hardcoded hex values in components. Change the JSON, regenerate, and every surface updates.

02Phase

The Atmospheric Layer

This is where most design systems stop at 'colors and fonts.' Arctic Midnight goes further: it defines the texture, depth, and grain of every surface.

Step 3

Add Global Grain

SVG feTurbulence noise overlay in your root layout. Fixed position, full viewport, pointer-events none. Film stock feel, barely perceptible at 0.03 opacity.

tsx
<svg className="grain-overlay" aria-hidden="true">
  <filter id="grain">
    <feTurbulence
      type="fractalNoise"
      baseFrequency="0.65"
      numOctaves="3"
      stitchTiles="stitch"
    />
  </filter>
  <rect width="100%" height="100%" filter="url(#grain)" />
</svg>

Grain budget: 0.03 for web UI. 0.25 for mesh gradients. 0.15 for video. Flat surfaces are cheap. Textured surfaces are premium.

Step 4

Build Mesh Gradients

The signature visual. Layer 5-7 radial-gradient ellipses at varying positions and sizes. Each gradient uses an aurora color at low opacity. Add dedicated black radial gradients for depth. Cover with SVG grain at 0.25 opacity.

aurora
ember
dracula

Critical learning: we started with linear-gradient and conic-gradient. They created hard, directional bands. Visible straight lines.

The fix was switching entirely to radial-gradient ellipses. Ellipses blend organically because their edges are always curved. No hard edges. No banding. Color emerges through darkness.

Tell Claude

Create an aurora-mesh.tsx component. Pure CSS, no JS animation. Use 5-7 radial-gradient ellipses with aurora colors at 0.3-0.5 opacity on a near-black background. Add 2 dedicated black radial gradients for depth. SVG grain overlay at 0.25 opacity, baseFrequency 0.85, 5 octaves. Create 5 variants: aurora, ember, frost, moss, dracula.

Step 5

Animate the Mesh

Port the blob positions into a requestAnimationFrame loop. Each blob drifts via Math.sin/cos with unique phase offsets. Falls back to static on reduced motion.

tsx
const animate = useCallback(() => {
  const t = performance.now() * speed
  const gradients = blobs.map((blob, i) => {
    const phase = i * 1.3
    const cx = blob.cx + Math.sin(t + phase) * blob.dx
    const cy = blob.cy + Math.cos(t * 0.7 + phase) * blob.dy
    return buildBlob(blob, cx, cy)
  })
  el.style.backgroundImage = gradients.join(', ')
  rafRef.current = requestAnimationFrame(animate)
}, [config, speed])
Animated / Slow
03Phase

Component Vocabulary

Every interactive element needs physics. Not just color changes. Define the patterns once, use them everywhere.

Step 6

Interaction Patterns

The magnetic button does not just change color on hover. It scales 1.02x and a purple wash slides up from underneath via ::after pseudo-element.

css
.magnetic-btn {
  position: relative;
  overflow: hidden;
  transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1);
}
.magnetic-btn:hover { transform: scale(1.02); }
.magnetic-btn::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--magnetic-wash);
  transform: translateY(100%);
  transition: transform 0.45s cubic-bezier(0.23, 1, 0.32, 1);
}
.magnetic-btn:hover::after { transform: translateY(0); }

Button content must be wrapped in <span className="relative z-10"> so the wash slides underneath. Every interactive element needs cursor-pointer.

Step 7

Typography Discipline

Two fonts. Strict rules.

ContextFontWeightTracking
HeadlinesJakarta Sansfont-medium (500)-0.04em
BodyJakarta Sansnormal (400)-0.02em
Labels, numbersGeist Monofont-medium on dark0.15em
Code, pathsGeist Mononormaldefault

Never use font-bold or font-semibold.

Boldness comes from size and tracking, not weight.

04Phase

The Refinement Loop

This is where vibe coding diverges from traditional design. You do not spec everything upfront. You build, look, react, refine.

The Feedback Pattern

  1. 01

    Tell Claude what to build

    Be specific about the surface, vague about the details. "Build a section that shows the aurora palette swatches with a mesh gradient demo below."

  2. 02

    Look at the result

    Run pnpm dev, open the page, use your eyes. No amount of description replaces visual inspection.

  3. 03

    React with direction

    Instead of "change the opacity to 0.7", say "the mono text is nearly invisible on the mesh." Name problems, not solutions.

  4. 04

    Dial the knobs

    "Darker." "Heavier on the black." "Dial up the grain." "More contrast." These are creative directions, not technical specs.

  5. 05

    Lock it down

    Run the quality checklist. Check cursor-pointer, border-sub, type scale, grain presence, reduced motion support.

Real Examples from Our Session

What we saidWhat Claude did
"There are weird straight lines"Replaced all linear-gradient + conic-gradient with radial-gradient ellipses
"Heavier on the black"Added dedicated black radial-gradient layers in every mesh palette
"Dial up the grain"Bumped opacity 0.18 to 0.25, baseFrequency 0.75 to 0.85, octaves 4 to 5
"Hard to read the Geist Mono"Bumped text-white/40 to /70, added font-medium, increased text-[10px] to text-[11px]
"Should we use toast?"Installed Sonner, configured with brand styling, rewired all copy buttons
"Vertically center the numbers"Changed items-start to items-center, removed mt-0.5 on number span
"Use semi-bold intelligently"Applied font-medium selectively to mono text on dark/busy backgrounds

Why this works: Claude holds the entire design system in context. When you say "dial up the grain," it knows the current grain opacity, the baseFrequency, and where grain is applied (page-level, mesh, video). It makes a coherent change across all surfaces. A Figma file cannot do this. A Sketch symbol cannot do this. Claude Code can.

05Phase

Build the Showcase

Every design system needs a self-documenting page. Not documentation. A living specimen where every swatch is the actual token and every animation is the actual CSS class.

Step 8

The Design System Page

Structure it as numbered sections with ghost numbers, eyebrow labels, and live interactive examples. Color swatches are clickable to copy. Code snippets are copy-paste ready. Mesh gradient demos render inline. Toast notifications for copy feedback.

text
01 / Brand      — Logo, lockup, usage rules
02 / Colors     — Light mode, dark mode, accent, utility tokens
03 / Type       — Font families, scale, labels
04 / Space      — Spacing tokens, containers, breakpoints
05 / Components — Buttons, cards, pills, inputs
06 / Motion     — Easing curves, animations, scroll reveal
07 / Layout     — Section structure, grids
08 / Aurora     — Palette, gradient, mesh variants, overlays
09 / Templates  — Remotion video compositions, downloads
10 / Export     — SKILL.md download, install commands
Step 9

Video Compositions

The mesh gradients are not just for web. Port them to Remotion. Same blob positions and colors. Use useCurrentFrame and interpolate for animation. Register compositions at 3840x2160 for each palette variant.

tsx
// Same blob math, Remotion's frame system
const frame = useCurrentFrame()
const cx = interpolate(
  Math.sin(frame * 0.003 * mult + phase),
  [-1, 1],
  [blob.cx - blob.dx, blob.cx + blob.dx]
)

Video editors drop the rendered MP4 into their timeline. The brand system renders itself.

06Phase

Package as a Skill

The design system becomes a Claude Code skill. When anyone invokes /design-system, Claude gains complete knowledge of every token, pattern, component, and convention.

Step 10

Create SKILL.md

A SKILL.md is not documentation. It is a persona injection. You tell Claude who it becomes when this skill is active.

yaml
---
name: Arctic Midnight Design System
description: Build UI, video overlays, and export assets using the Arctic Midnight system.
---

# Arctic Midnight Design System

You are three people. You are a principal-level Brand Architect,
a principal-level UX Engineer, and a principal-level Motion Designer.

Then the body: roles, tokens, typography rules, component patterns, mesh gradient docs, animation catalog, layout patterns, quality checklist.

Step 11

Serve the Download

Put the SKILL.md in your public directory. Add a download button and curl command.

bash
mkdir -p .claude/skills/design-system
curl -o .claude/skills/design-system/SKILL.md \
  https://modh.ca/exports/design-system-skill.md

Get the Skill

The entire Arctic Midnight design system, encoded as a Claude Code skill. Drop it into any project.

07Phase

Lessons Learned

What we discovered building Arctic Midnight through iterative vibe coding with Claude Code.

01

Start with tokens, not components

If your CSS variables are right, the components practically write themselves. If your tokens are wrong, every component fights the system.

02

Grain makes everything premium

A flat gradient looks digital. A grainy gradient looks cinematic. The difference between "AI-generated" and "designed" is often just texture.

03

Use radial-gradient for organic blending

Linear gradients create bands. Conic gradients create pinwheels. Radial gradient ellipses create soft, organic color fields. Always use ellipses for more natural shapes.

04

Black radial gradients create depth

Layer 1-2 dedicated black radial gradients in every mesh palette. Color should emerge through darkness, not sit on top of it.

05

Never use font-bold

In a well-designed system, hierarchy comes from size and tracking, not weight. font-medium (500) is the maximum. Use it on headings and mono text on dark backgrounds.

06

Name problems, not solutions

When giving feedback to Claude, describe what feels wrong, not what to change. "Hard to read" is better than "change the opacity." Claude holds the full context.

07

Toast notifications are table stakes

If a user copies something, they need instant feedback. Install Sonner. Brand the toast styling. Wire every copy action.

08

Accessibility is not optional

prefers-reduced-motion support in every animation. aria-hidden on decorative elements. Minimum contrast ratios on text overlays. These are requirements.

09

The SKILL.md is the ultimate export

When you encode your design system as a Claude Code skill, anyone with Claude Code can build in your system. That is the real deliverable.