How-to

Generating Design-System UI with Claude Every Time

How to use FramingUI with Claude Code to generate design-system-aligned UI components every time.

FramingUI Team5 min read

Left unconstrained, Claude Code generates a different design system on every run. Ask it for a card component on Monday, a table on Wednesday, and a form on Friday — you get three color palettes, three spacing rhythms, and three interpretations of what a "button" should look like. The code works. The product doesn't cohere.

The root issue is that Claude optimizes for plausibility, not your system. It fills in missing context from training data, which means public codebases, not your design decisions. Giving Claude structured access to your actual tokens and components changes what "plausible" means.

What FramingUI Provides

FramingUI gives Claude a concrete vocabulary to work from:

  • Type-safe components from @framingui/ui with enumerated variants and sizes
  • Semantic CSS variables like var(--bg-primary) and var(--spacing-4) that carry intent
  • Consistent component APIs that constrain generation at the interface level

When Claude has this vocabulary, it composes from your building blocks instead of inventing new ones.

Teaching Claude About Your Design System

The fastest way to get consistent output is a project knowledge file that Claude reads at the start of every session. Create .claude/project-knowledge.md:

# Project Design System

This project uses FramingUI — a component library built on shadcn-ui with integrated design tokens.

## Components

Import from `@framingui/ui`:
- Layout: Card, Sheet, Dialog, Tabs, Separator
- Forms: Button, Input, Label, Checkbox, RadioGroup, Select, Switch, Textarea
- Data: Table, Avatar, Badge
- Feedback: Toast, Tooltip, Progress

## Design Tokens

All styling uses CSS custom properties:

**Colors:** `var(--bg-primary)`, `var(--bg-secondary)`, `var(--bg-destructive)`,
`var(--bg-muted)`, `var(--bg-card)`, `var(--bg-accent)`

**Spacing:** `var(--spacing-1)` through `var(--spacing-8)`

**Border radius:** `var(--radius-sm)`, `var(--radius-md)`, `var(--radius-lg)`

## Rules for Code Generation

- Never hardcode colors. Always use `var(--*)` tokens.
- Import components from `@framingui/ui`, not local files.
- Use semantic names: `primary`, not `blue`.
- Use `<Button>` instead of `<button>`.

Install the Package

pnpm add @framingui/ui @framingui/core @framingui/tokens tailwindcss-animate

Import Tokens

// app/layout.tsx
import '@framingui/ui/styles';

The Difference Explicit Context Makes

Here's what the same prompt produces with and without FramingUI context.

Prompt: Create a user profile card with name, email, and edit button

Without FramingUI context

export function ProfileCard({ name, email }: ProfileCardProps) {
  return (
    <div className="bg-white p-6 rounded-lg shadow-md border border-gray-200">
      <h3 className="text-xl font-bold text-gray-900">{name}</h3>
      <p className="text-gray-600 text-sm mt-1">{email}</p>
      <button className="mt-4 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
        Edit Profile
      </button>
    </div>
  );
}

Hardcoded colors, arbitrary spacing, won't adapt to theme changes.

With FramingUI context

import { Card, CardHeader, CardTitle, CardContent, CardFooter, Button } from '@framingui/ui';

export function ProfileCard({ name, email }: ProfileCardProps) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>{name}</CardTitle>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-[var(--bg-muted-foreground)]">{email}</p>
      </CardContent>
      <CardFooter>
        <Button variant="default">Edit Profile</Button>
      </CardFooter>
    </Card>
  );
}

Semantic tokens, FramingUI primitives, automatic theme adaptation.

Building a Dashboard in Two Prompts

Constrained prompts let Claude build complex, coherent UIs without drift.

Prompt 1: Stats cards

Create 4 stat cards showing Total Users, Revenue, Active Sessions, and Conversion Rate.
Use FramingUI Card component. Use design tokens for all spacing and color.

Claude generates:

import { Card, CardContent } from '@framingui/ui';
import { Users, DollarSign, Activity, TrendingUp } from 'lucide-react';

const stats = [
  { title: 'Total Users', value: '12,345', icon: Users, change: '+12%' },
  { title: 'Revenue', value: '$54,321', icon: DollarSign, change: '+8%' },
  { title: 'Active Sessions', value: '1,234', icon: Activity, change: '+23%' },
  { title: 'Conversion Rate', value: '3.2%', icon: TrendingUp, change: '+0.5%' },
];

export function StatsGrid() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[var(--spacing-4)]">
      {stats.map((stat) => (
        <Card key={stat.title}>
          <CardContent className="p-[var(--spacing-6)]">
            <div className="flex items-center justify-between">
              <div>
                <p className="text-sm text-[var(--bg-muted-foreground)]">{stat.title}</p>
                <p className="text-2xl font-bold mt-[var(--spacing-2)]">{stat.value}</p>
                <p className="text-sm text-[var(--bg-primary)] mt-[var(--spacing-1)]">{stat.change}</p>
              </div>
              <div className="p-[var(--spacing-3)] bg-[var(--bg-primary)]/10 rounded-[var(--radius-lg)]">
                <stat.icon className="w-6 h-6 text-[var(--bg-primary)]" />
              </div>
            </div>
          </CardContent>
        </Card>
      ))}
    </div>
  );
}

All spacing uses tokens. Colors are semantic.

Prompt 2: Users table

Add a users table with Name, Email, Role, and Status columns.
Use FramingUI Table and Badge components.

Claude generates consistent output — same token vocabulary, same component primitives, visually coherent with the stats cards above.

Extending with Custom Tokens

For app-specific layout constants, define them alongside the FramingUI token import:

/* globals.css */
@import '@framingui/ui/styles';

:root {
  --app-sidebar-width: 280px;
  --app-header-height: 64px;
}

Add these to your project knowledge file, and Claude will use them in layout prompts:

Create a dashboard layout with sidebar and header using our custom layout tokens.

The result will reference var(--app-sidebar-width) and var(--app-header-height) correctly.

Prompting Patterns That Work

A few patterns that consistently improve output quality:

Name the component explicitly. "Create a modal using FramingUI Dialog" produces better results than "Create a modal."

Reference tokens directly. "Use var(--bg-muted-foreground) for secondary text" is clearer than "make the text gray."

Use variant names. "Button variant='destructive'" communicates intent better than "make the button red."

Add a consistency check. After generating a complex component, follow up with: "Review the code and replace any hardcoded color or spacing values with semantic tokens."

Auditing for Token Usage

Check generated code for token compliance:

# Hardcoded colors (should return no matches)
grep -r "bg-blue\|text-red\|#[0-9A-Fa-f]\{6\}" src/

# Token usage (should return many matches)
grep -r "var(--" src/

Run this after any large generation session. If you find violations, ask Claude to fix them before committing.


The project knowledge file does most of the work. Fifteen minutes to write it, and every subsequent Claude Code session starts with a shared vocabulary instead of a blank slate.

Ready to build with FramingUI?

Build consistent UI with AI-ready design tokens. No more hallucinated colors or spacing.

Try FramingUI
Share

Related Posts