03. Design System & Atomic Components — Vibe Coding Series

Build a design system before writing UI code — use AI to generate design tokens, atomic components, and maintain consistency throughout your project.

I asked AI to write UI for TaskFlow five times. First time, the button was indigo. Second time, blue. Third time, violet. Fourth time, AI added a gradient on its own. By the fifth time, I opened the app and saw a mess — every screen looked like it was designed by a different person.

AI didn’t do anything wrong. I never told it what the style was supposed to be.


Context

In article 02, I locked down the API contract for TaskFlow — OpenAPI spec, mock server, and AI_CONTEXT.md to give AI agents clear context. This article moves into Phase 2.5: before writing a single line of UI code, we need a design system — a shared design language that both you and AI understand and follow.


Design tokens are the foundation

Design tokens are the most atomic values in a design system: colors, typography, spacing, border radius, shadows. Instead of saying “blue button,” you say “button uses color.primary.600.” Instead of “reasonable padding,” you say “padding spacing.4.”

Tokens solve the root problem: AI has no concept of “your brand’s blue” or “appropriate spacing” — but it can absolutely follow a concrete token system.

AI generating tokens from a brand brief

I used this prompt to create the initial tokens for TaskFlow:

Prompt:
"TaskFlow is a task management SaaS for indie teams. Brand personality:
professional but not corporate, clean, focused on productivity.
Primary color: blue-ish. Create a design token system with:
- Color palette (primary, neutral, semantic: success/warning/error)
- Typography scale (font size, weight, line height)
- Spacing scale (4px base)
- Border radius, shadow
Format: CSS custom properties."

AI returned a complete token file. I reviewed it, adjusted a few values, and here’s the final result for TaskFlow:

/* tokens.css */
:root {
  /* Color — Primary */
  --color-primary-50: #eff6ff;
  --color-primary-100: #dbeafe;
  --color-primary-500: #3b82f6;
  --color-primary-600: #2563eb;
  --color-primary-700: #1d4ed8;

  /* Color — Neutral */
  --color-neutral-0: #ffffff;
  --color-neutral-50: #f8fafc;
  --color-neutral-100: #f1f5f9;
  --color-neutral-200: #e2e8f0;
  --color-neutral-500: #64748b;
  --color-neutral-700: #334155;
  --color-neutral-900: #0f172a;

  /* Color — Semantic */
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-error: #ef4444;

  /* Typography */
  --font-size-xs: 0.75rem;    /* 12px */
  --font-size-sm: 0.875rem;   /* 14px */
  --font-size-base: 1rem;     /* 16px */
  --font-size-lg: 1.125rem;   /* 18px */
  --font-size-xl: 1.25rem;    /* 20px */
  --font-size-2xl: 1.5rem;    /* 24px */

  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;

  --line-height-tight: 1.25;
  --line-height-normal: 1.5;

  /* Spacing (4px base) */
  --spacing-1: 0.25rem;   /* 4px */
  --spacing-2: 0.5rem;    /* 8px */
  --spacing-3: 0.75rem;   /* 12px */
  --spacing-4: 1rem;      /* 16px */
  --spacing-6: 1.5rem;    /* 24px */
  --spacing-8: 2rem;      /* 32px */

  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-full: 9999px;

  /* Shadow */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}

If you’re using Tailwind, AI can generate an equivalent tailwind.config.ts with theme.extend — same logic, different format.


Atomic component approach

Atomic Design splits components into three layers:

  • Atom: the smallest unit, can’t be broken down further — Button, Input, Badge, Typography
  • Molecule: combines 2–3 atoms — SearchBar (Input + Button), FormField (Label + Input + ErrorText)
  • Organism: combines multiple molecules — TaskCard, NavigationBar, TaskForm

With AI, I only generate atoms first. Molecules and organisms get composed from atoms — AI will know how to combine them once the atoms are properly defined.

Using AI to generate components with tokens

The prompt pattern I use for each atom:

Prompt:
"Create a React [ComponentName] component with:
- Use CSS custom properties from the defined tokens.css
- Props: [list props and types]
- Variants: [list variants]
- Accessibility: appropriate aria attributes
- TypeScript, no inline styles"

The key detail: I always mention “use CSS custom properties from tokens.css” — this is how you feed the design system into AI context at the prompt level.

Storybook for documentation

After getting a component, I ask AI to generate the corresponding Storybook story:

Prompt:
"Create a Storybook story for the Button component. Include stories for
all variants (primary, secondary, ghost), sizes (sm, md, lg),
and states (default, disabled, loading)."

Storybook isn’t required for small projects — but it’s “living documentation” that lets both you and AI see what a component looks like before integrating it into the app.


AI and consistency

Feed the design system into AI_CONTEXT.md

Once I had tokens and a component library, I updated AI_CONTEXT.md with a new section:

## Design System

### Tokens
- File: /src/styles/tokens.css
- Color: --color-primary-{50|100|500|600|700}, --color-neutral-{0|50|100|200|500|700|900}
- Semantic: --color-success, --color-warning, --color-error
- Spacing: --spacing-{1|2|3|4|6|8} (4px base)
- Typography: --font-size-{xs|sm|base|lg|xl|2xl}, --font-weight-{normal|medium|semibold}

### Component Library
- Atoms: Button, Input, Card, Badge, Typography
- Location: /src/components/ui/
- Usage: import from @/components/ui, do not create new styles

### Rules
- DO NOT use hardcoded color values (e.g., #3b82f6) — always use tokens
- DO NOT create new components if an atom already exists — compose from existing components
- Every interactive element must have a focus state and aria label

From here, every time AI generates UI code, it has full context about the design system. No more AI inventing colors or spacing.

Reviewing AI-generated components

When AI returns a component, I check 3 things:

  1. Token compliance: any hardcoded values? (#3b82f6 instead of var(--color-primary-500))
  2. Accessibility: are aria-label, role, focus states present?
  3. Prop API: is it consistent with other components? (naming conventions, variant patterns)

Use AI to review the code AI just generated:

Prompt:
"Review this component and check:
1. Any hardcoded color/spacing instead of CSS tokens?
2. Accessibility: what aria attributes are missing?
3. Is the prop API consistent with the existing Button component?"

Real-world example: 5 atom components for TaskFlow

Here are the 5 atoms I built for TaskFlow, in order:

1. Typography — first, because every other component uses text

2. Badge — simplest, only has color variants

3. Button — more complex, has variants, sizes, and loading state

4. Input — has error state, label, helper text

5. Card — container component, uses shadow and border tokens

Here’s the Button component — the most complex atom of the five:

// src/components/ui/Button.tsx
import type { ButtonHTMLAttributes } from "react";

type ButtonVariant = "primary" | "secondary" | "ghost";
type ButtonSize = "sm" | "md" | "lg";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  loading?: boolean;
}

const variantStyles: Record<ButtonVariant, string> = {
  primary: "btn-primary",
  secondary: "btn-secondary",
  ghost: "btn-ghost",
};

const sizeStyles: Record<ButtonSize, string> = {
  sm: "btn-sm",
  md: "btn-md",
  lg: "btn-lg",
};

export function Button({
  variant = "primary",
  size = "md",
  loading = false,
  disabled,
  children,
  className = "",
  ...props
}: ButtonProps) {
  return (
    <button
      className={`btn ${variantStyles[variant]} ${sizeStyles[size]} ${className}`}
      disabled={disabled || loading}
      aria-busy={loading}
      {...props}
    >
      {loading ? <span className="btn-spinner" aria-hidden="true" /> : null}
      {children}
    </button>
  );
}
/* src/styles/components/button.css */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  font-weight: var(--font-weight-medium);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color 150ms, opacity 150ms;
}

.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }

.btn-primary {
  background: var(--color-primary-600);
  color: var(--color-neutral-0);
  border: none;
}
.btn-primary:hover:not(:disabled) { background: var(--color-primary-700); }

.btn-secondary {
  background: var(--color-neutral-0);
  color: var(--color-neutral-700);
  border: 1px solid var(--color-neutral-200);
}
.btn-secondary:hover:not(:disabled) { background: var(--color-neutral-50); }

.btn-ghost {
  background: transparent;
  color: var(--color-neutral-700);
  border: none;
}
.btn-ghost:hover:not(:disabled) { background: var(--color-neutral-100); }

.btn-sm { padding: var(--spacing-1) var(--spacing-3); font-size: var(--font-size-sm); }
.btn-md { padding: var(--spacing-2) var(--spacing-4); font-size: var(--font-size-base); }
.btn-lg { padding: var(--spacing-3) var(--spacing-6); font-size: var(--font-size-lg); }

Every value uses tokens — no hardcoded colors or spacing anywhere. When I want to change the primary color across the entire app, I only need to update --color-primary-600 in tokens.css.


Pitfalls

Skipping the design system and letting AI decide the style. This is the fastest way to end up with a patchwork app. AI has no consistent aesthetic — every generation is a roll of the dice. Spending 2–3 hours setting up tokens and 5 atom components will save you dozens of hours of refactoring later.

Over-engineering the design system for a small project. On the flip side, don’t build a Figma token pipeline, design-to-code automation, and multi-theme support for an MVP. For a small project, a single tokens.css file and 5–7 atom components is enough. Complexity can be added later once you know what you actually need.


Summary

  • Design tokens are the shared language between you and AI — define once, use everywhere
  • AI can generate tokens from a brand brief, but you need to review and adjust
  • Atomic approach: build atoms first, compose molecules and organisms after
  • Update AI_CONTEXT.md with design system rules — this is the most effective way to feed context
  • Review AI-generated components: token compliance, accessibility, prop API consistency

What’s Next

Phase 3 tackles the question: now that you have a design system and API contract, how do you break features into tasks with the right granularity for AI agents? Tasks too large and AI loses context; tasks too small and the overhead kills your flow. The next article covers task planning and test strategy.

→ Article 04: Task Planning & Test Strategy

← Article 02: Specs & API Contract First