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:
- Token compliance: any hardcoded values? (
#3b82f6instead ofvar(--color-primary-500)) - Accessibility: are
aria-label,role, focus states present? - 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.mdwith 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.