Design-to-Component Translator
Convert design specifications into pixel-perfect, production-ready React components.
Core Workflow
- Analyze design specs: Extract spacing, colors, typography, dimensions
- Map to tokens: Convert design values to design system tokens
- Generate structure: Create semantic HTML structure
- Apply styles: Implement Tailwind/CSS with exact measurements
- Add states: Include hover, focus, active, disabled states
- Handle responsive: Implement breakpoint-specific rules
- Ensure accessibility: Add ARIA labels, keyboard navigation
- Document variants: List all visual states and props
Design Spec Analysis
Extract from Figma/Design
Spacing & Layout:
- Padding:
p-4 (16px), px-6 (24px horizontal)
- Margin:
m-2 (8px), mt-4 (16px top)
- Gap:
gap-3 (12px between flex items)
- Width/Height:
w-64 (256px), h-10 (40px)
Typography:
- Font family:
font-sans, font-mono
- Font size:
text-sm (14px), text-base (16px), text-lg (18px)
- Font weight:
font-normal (400), font-medium (500), font-semibold (600)
- Line height:
leading-tight, leading-normal, leading-relaxed
- Letter spacing:
tracking-tight, tracking-normal, tracking-wide
Colors:
- Background:
bg-blue-500, bg-gray-100
- Text:
text-gray-900, text-white
- Border:
border-gray-300, border-blue-600
- Opacity:
bg-opacity-50, text-opacity-75
Borders & Radius:
- Border width:
border, border-2, border-t-4
- Border radius:
rounded (4px), rounded-md (6px), rounded-lg (8px), rounded-full
Shadows:
shadow-sm, shadow, shadow-md, shadow-lg, shadow-xl
Design Token Mapping
Create Token System
// tokens.ts
export const tokens = {
colors: {
primary: {
50: "#eff6ff",
100: "#dbeafe",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
},
gray: {
100: "#f3f4f6",
300: "#d1d5db",
500: "#6b7280",
700: "#374151",
900: "#111827",
},
},
spacing: {
xs: "0.25rem", // 4px
sm: "0.5rem", // 8px
md: "1rem", // 16px
lg: "1.5rem", // 24px
xl: "2rem", // 32px
},
fontSize: {
xs: ["0.75rem", { lineHeight: "1rem" }],
sm: ["0.875rem", { lineHeight: "1.25rem" }],
base: ["1rem", { lineHeight: "1.5rem" }],
lg: ["1.125rem", { lineHeight: "1.75rem" }],
xl: ["1.25rem", { lineHeight: "1.75rem" }],
},
borderRadius: {
sm: "0.25rem", // 4px
md: "0.375rem", // 6px
lg: "0.5rem", // 8px
full: "9999px",
},
shadows: {
sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
md: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
lg: "0 10px 15px -3px rgb(0 0 0 / 0.1)",
},
};
Tailwind Config
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: {
50: "#eff6ff",
100: "#dbeafe",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
},
},
spacing: {
18: "4.5rem",
88: "22rem",
},
fontSize: {
"2xs": "0.625rem",
},
},
},
};
Component Translation Examples
Button from Figma Spec
Figma Specs:
- Height: 40px
- Padding: 12px 24px
- Border radius: 6px
- Font: Inter Medium 14px
- Background: #3B82F6
- Text: #FFFFFF
- Hover: #2563EB
- Shadow: 0 1px 3px rgba(0,0,0,0.1)
Translated Component:
import { cn } from "@/lib/utils";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
}
export const Button = ({
variant = "primary",
size = "md",
className,
children,
...props
}: ButtonProps) => {
return (
<button
className={cn(
// Base styles
"inline-flex items-center justify-center rounded-md font-medium",
"transition-colors duration-200",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2",
"disabled:pointer-events-none disabled:opacity-50",
// Variant: Primary (matches Figma)
variant === "primary" && [
"bg-primary-500 text-white shadow-sm",
"hover:bg-primary-600",
"active:bg-primary-700",
],
// Size: Medium (40px height, 12px 24px padding)
size === "md" && "h-10 px-6 text-sm",
className
)}
{...props}
>
{children}
</button>
);
};
Card from Design Spec
Figma Specs:
- Padding: 24px
- Border radius: 12px
- Background: #FFFFFF
- Border: 1px solid #E5E7EB
- Shadow: 0 1px 3px rgba(0,0,0,0.1)
- Max width: 400px
Translated Component:
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
elevated?: boolean;
}
export const Card = ({
elevated = false,
className,
children,
...props
}: CardProps) => {
return (
<div
className={cn(
// Base from Figma
"max-w-sm rounded-xl bg-white p-6",
"border border-gray-200",
// Conditional shadow
elevated ? "shadow-lg" : "shadow-sm",
// Hover state (not in Figma, but good UX)
"transition-shadow duration-200 hover:shadow-md",
className
)}
{...props}
>
{children}
</div>
);
};
Interaction States
Hover States
// Figma: Background changes from #3B82F6 to #2563EB on hover
className={cn(
'bg-primary-500',
'hover:bg-primary-600',
'transition-colors duration-200'
)}
Focus States
// Accessible focus ring
className={cn(
'focus:outline-none',
'focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2'
)}
Active/Pressed States
// Figma: Slightly darker on click
className={cn(
'active:bg-primary-700',
'active:scale-[0.98]', // Slight scale down
'transition-all duration-100'
)}
Disabled States
// Figma: 50% opacity, no interactions
className={cn(
'disabled:opacity-50',
'disabled:cursor-not-allowed',
'disabled:pointer-events-none'
)}
Responsive Design Translation
Breakpoint Mapping
// Figma artboards → Tailwind breakpoints
// Mobile (375px): default (no prefix)
// Tablet (768px): md:
// Desktop (1024px): lg:
// Wide (1280px): xl:
<div
className={cn(
// Mobile: Stack vertically, full width
"flex flex-col gap-4 w-full",
// Tablet: Side by side, 50% each
"md:flex-row md:gap-6",
// Desktop: Max width container
"lg:max-w-6xl lg:mx-auto"
)}
/>
Responsive Typography
// Figma mobile: 14px, desktop: 16px
<h1 className="text-sm md:text-base lg:text-lg font-semibold">
Responsive Heading
</h1>
Responsive Spacing
// Figma mobile: 16px padding, desktop: 24px
<div className="p-4 md:p-6 lg:p-8">Content</div>
Design System Integration
Using shadcn/ui Patterns
// Leveraging shadcn's composable approach
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
Color System Translation
From Figma to CSS Variables
/* Figma colors → CSS variables */
:root {
/* Primary from Figma #3B82F6 */
--primary: 221 83% 60%;
--primary-foreground: 0 0% 100%;
/* Secondary from Figma #6B7280 */
--secondary: 220 9% 46%;
--secondary-foreground: 0 0% 100%;
/* Backgrounds */
--background: 0 0% 100%;
--foreground: 222 47% 11%;
/* Borders */
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 221 83% 60%;
/* Radius from Figma */
--radius: 0.5rem;
}
Using in Components
<div className="bg-background text-foreground border-border">
<button className="bg-primary text-primary-foreground">Button</button>
</div>
Animation & Transitions
Micro-interactions from Figma
// Figma: Button scales slightly on hover
<button className={cn(
'transition-all duration-200',
'hover:scale-105',
'active:scale-95'
)}>
Hover me
</button>
// Figma: Card lifts on hover
<div className={cn(
'transition-all duration-300',
'hover:-translate-y-1 hover:shadow-lg'
)}>
Card content
</div>
// Figma: Fade in on mount
<div className="animate-in fade-in duration-500">
Fading content
</div>
Measurement Conversion
Figma Pixels → Tailwind Classes
| Figma |
Tailwind |
Value |
| 2px |
0.5 |
0.125rem |
| 4px |
1 |
0.25rem |
| 8px |
2 |
0.5rem |
| 12px |
3 |
0.75rem |
| 16px |
4 |
1rem |
| 20px |
5 |
1.25rem |
| 24px |
6 |
1.5rem |
| 32px |
8 |
2rem |
| 40px |
10 |
2.5rem |
| 48px |
12 |
3rem |
Custom Values
// Figma: 18px (not in default Tailwind)
<div className="w-[18px] h-[18px]">{/* or add to config */}</div>
Accessibility Mapping
From Visual Design to A11y
// Figma shows disabled state
<button
disabled={isDisabled}
aria-disabled={isDisabled}
className={cn(
isDisabled && 'opacity-50 cursor-not-allowed'
)}
>
Submit
</button>
// Figma shows error state
<input
aria-invalid={hasError}
aria-describedby={hasError ? 'error-message' : undefined}
className={cn(
hasError && 'border-red-500 focus:ring-red-500'
)}
/>
Common Patterns
Form Input Translation
Figma Specs:
- Height: 44px
- Padding: 12px 16px
- Border: 1px #D1D5DB
- Border radius: 8px
- Focus border: 2px #3B82F6
<input
className={cn(
"h-11 w-full rounded-lg border border-gray-300 px-4",
"text-base text-gray-900 placeholder:text-gray-500",
"focus:border-primary-500 focus:ring-2 focus:ring-primary-500 focus:ring-offset-0",
"disabled:cursor-not-allowed disabled:opacity-50"
)}
/>
Icon Button Translation
Figma Specs:
- Size: 40x40px
- Icon: 20x20px centered
- Border radius: 8px
- Background hover: #F3F4F6
<button
className={cn(
"flex h-10 w-10 items-center justify-center rounded-lg",
"text-gray-700 transition-colors",
"hover:bg-gray-100",
"focus-visible:ring-2 focus-visible:ring-primary-500"
)}
>
<Icon className="h-5 w-5" />
</button>
Best Practices
- Measure twice: Verify all measurements match Figma exactly
- Use design tokens: Map to tokens, not hardcoded values
- All states: Include hover, focus, active, disabled, error
- Responsive: Implement all breakpoints from design
- Accessibility: Add ARIA where Figma shows states
- Animations: Match transition timings to design
- Dark mode: If designs exist, implement with class variants
- Component variants: Create reusable variant props
- Documentation: Note any deviations from design
- Review: Get designer approval on implementation
Output Checklist
Every design-to-component translation should include: