The Claude Code Challenge
Claude Code is exceptional at understanding context and generating production-ready code. However, without explicit design constraints, it will:
- Invent color schemes — "I'll make this button blue" →
bg-blue-600 - Guess spacing patterns — Mix
p-4,px-5,gap-3inconsistently - Create one-off styles — Each component gets unique styling
The result? A codebase where every AI-generated component looks different.
FramingUI: Claude's Design Guardrails
FramingUI provides Claude with a structured design vocabulary through:
- Type-safe components from
@framingui/ui - Semantic design tokens like
var(--tekton-bg-primary) - Consistent patterns enforced through component APIs
Setup: Teaching Claude About FramingUI
Step 1: Create a Project Knowledge File
Create .claude/project-knowledge.md:
# Project Design System
## FramingUI Component Library
This project uses **FramingUI**, a shadcn-ui fork with integrated design tokens.
### Available 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
- Navigation: Breadcrumb, NavigationMenu
### Design Tokens
All styling uses CSS custom properties:
**Colors:**
- `var(--tekton-bg-primary)` - Primary brand color
- `var(--tekton-bg-secondary)` - Secondary accent
- `var(--tekton-bg-destructive)` - Error/danger state
- `var(--tekton-bg-muted)` - Muted backgrounds
- `var(--tekton-bg-accent)` - Accent highlights
- `var(--tekton-bg-card)` - Card backgrounds
**Spacing:**
- `var(--tekton-spacing-1)` through `var(--tekton-spacing-8)`
**Border Radius:**
- `var(--tekton-radius-sm)`, `var(--tekton-radius-md)`, `var(--tekton-radius-lg)`, `var(--tekton-radius-xl)`
### Code Generation Rules
1. **Never hardcode colors** - Always use `var(--tekton-*)` tokens
2. **Import from @framingui/ui** - Not from local component files
3. **Use semantic names** - `primary`, not `blue`
4. **Prefer components over HTML** - Use `<Button>` instead of `<button>`
Step 2: Install FramingUI
pnpm add @framingui/ui @framingui/core
Step 3: Import Tokens
// app/layout.tsx
import '@framingui/ui/styles/tokens.css';
Example: Prompting Claude for Consistent UI
❌ Without FramingUI
Prompt:
Create a user profile card with name, email, and edit button
Claude generates:
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>
);
}
Issues:
- Hardcoded colors:
bg-white,text-gray-900,bg-blue-600 - Arbitrary spacing:
p-6,px-4 py-2 - Won't adapt to theme changes
✅ With FramingUI
Prompt:
Create a user profile card using FramingUI components with name, email, and edit button
Claude generates:
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(--tekton-bg-muted-foreground)]">{email}</p>
</CardContent>
<CardFooter>
<Button variant="default">Edit Profile</Button>
</CardFooter>
</Card>
);
}
Benefits:
- Uses FramingUI Card components
- Semantic tokens:
var(--tekton-bg-muted-foreground) - Automatically adapts to theme
- Consistent with other components
Real-World Example: Building a Dashboard
Let's build a complete dashboard with Claude using FramingUI.
Prompt 1: Stats Cards
Create 4 stat cards showing Total Users, Revenue, Active Sessions, and Conversion Rate.
Use FramingUI Card component and design tokens.
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(--tekton-spacing-4)]">
{stats.map((stat) => (
<Card key={stat.title}>
<CardContent className="p-[var(--tekton-spacing-6)]">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-[var(--tekton-bg-muted-foreground)]">
{stat.title}
</p>
<p className="text-2xl font-bold mt-[var(--tekton-spacing-2)]">
{stat.value}
</p>
<p className="text-sm text-[var(--tekton-bg-primary)] mt-[var(--tekton-spacing-1)]">
{stat.change}
</p>
</div>
<div className="p-[var(--tekton-spacing-3)] bg-[var(--tekton-bg-primary)]/10 rounded-[var(--tekton-radius-lg)]">
<stat.icon className="w-6 h-6 text-[var(--tekton-bg-primary)]" />
</div>
</div>
</CardContent>
</Card>
))}
</div>
);
}
✅ Notice: All spacing uses tokens, colors are semantic
Prompt 2: Data Table
Add a users table below the stats with columns: Name, Email, Role, Status.
Use FramingUI Table component.
Claude generates:
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Badge } from '@framingui/ui';
const users = [
{ name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', status: 'active' },
{ name: 'Bob Smith', email: 'bob@example.com', role: 'User', status: 'active' },
{ name: 'Carol White', email: 'carol@example.com', role: 'User', status: 'inactive' },
];
export function UsersTable() {
return (
<Card>
<CardHeader>
<CardTitle>Recent Users</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.email}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell className="text-[var(--tekton-bg-muted-foreground)]">
{user.email}
</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>
<Badge variant={user.status === 'active' ? 'default' : 'secondary'}>
{user.status}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
);
}
✅ Notice: Badge variants, semantic color tokens, consistent spacing
Advanced: Custom Token Extensions
You can guide Claude to use custom tokens for app-specific patterns:
/* globals.css */
@import '@framingui/ui/styles/tokens.css';
:root {
--app-sidebar-width: 280px;
--app-header-height: 64px;
}
Update project knowledge:
### Custom Tokens
- `--app-sidebar-width` - Sidebar width (280px)
- `--app-header-height` - Header height (64px)
Prompt:
Create a dashboard layout with sidebar and header using our custom tokens
Claude generates:
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen">
<aside className="w-[var(--app-sidebar-width)] border-r border-[var(--tekton-border-default)]">
{/* Sidebar content */}
</aside>
<div className="flex-1 flex flex-col">
<header className="h-[var(--app-header-height)] border-b border-[var(--tekton-border-default)]">
{/* Header content */}
</header>
<main className="flex-1 overflow-auto p-[var(--tekton-spacing-6)]">
{children}
</main>
</div>
</div>
);
}
Tips for Better Claude + FramingUI Results
1. Be Specific About Components
❌ "Create a modal" ✅ "Create a modal using FramingUI Dialog component"
2. Reference Design Tokens in Prompts
❌ "Make the text gray" ✅ "Use var(--tekton-bg-muted-foreground) for the text color"
3. Ask for Semantic Variants
❌ "Make the button red" ✅ "Use Button variant='destructive'"
4. Iterate with Consistency Checks
Review the code and ensure all colors use var(--tekton-*) tokens
Measuring Success
After setting up FramingUI with Claude, audit your components:
# Check for hardcoded colors (should be 0)
grep -r "bg-blue\|text-red\|#[0-9A-F]\{6\}" src/
# Check for token usage (should be everywhere)
grep -r "var(--tekton-" src/
Next Steps
- Add project knowledge - Create
.claude/project-knowledge.md - Install FramingUI -
pnpm add @framingui/ui - Test with prompts - Ask Claude to generate components
- Verify consistency - Check that all components use tokens
With FramingUI, Claude Code becomes a design-system-aware code generator, producing consistent, production-ready UI every single time.