From Figma to Code: How I Close the Design–Engineering Gap
The handoff between design and engineering is where quality goes to die. Here's the workflow I use to keep intent intact from the first frame to the final commit.
The design–engineering gap isn't a people problem. It's a process problem. Designers make decisions in a tool that doesn't run in a browser. Engineers implement in a tool that doesn't think in pixels. Somewhere between the two, detail gets lost, intent gets diluted, and the product that ships looks like a photocopy of a photocopy of the original design.
I've spent a lot of time on both sides of that gap. Here's the workflow that's helped me close it.
Start With Decisions, Not Screens
The most common mistake I see in design processes is going straight to high-fidelity screens before the decisions that underpin them have been made.
What decisions need to be made first?
- What are the states this component can be in?
- What happens at each breakpoint?
- What's the empty state? The error state? The loading state?
- What are the edge cases — long text, no data, slow network?
A Figma frame shows the happy path. The work is in the states you didn't design for. I use a simple checklist before marking any component design as complete:
□ Default / rest state
□ Hover / focus state
□ Active / pressed state
□ Loading state
□ Empty state
□ Error state
□ Mobile (375px)
□ Tablet (768px)
□ Desktop (1280px)
□ Long content (50+ char labels, overflow)
□ No content (zero items in list)If any of these are unresolved, the design isn't done.
Design Tokens First
Design tokens are the contract between design and engineering. They're the named values — colours, spacing, typography scales, border radii — that both sides reference.1
In Figma, I use variables to define tokens. In code, I use CSS custom properties that map to the same names:
/* CSS tokens — mirror Figma variable names exactly */
:root {
--color-foreground: #0a0a0a;
--color-background: #fafafa;
--color-muted: #f4f4f5;
--color-border: #e4e4e7;
--space-1: 4px;
--space-2: 8px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--radius-sm: 6px;
--radius-md: 12px;
--radius-lg: 20px;
}When the designer changes the primary colour in Figma variables and the engineer updates the CSS custom property, every component using that token updates automatically. This is what makes large-scale changes safe instead of terrifying.
Component Anatomy Before Implementation
Before I write a single line of component code, I map the anatomy:
Props: What inputs does this component need? What's required vs. optional?
Variants: What visual variations exist? (Size, intent, state)
Slots: Where does content get injected? Children, icons, labels?
Behaviour: What interactions does it have? What events does it emit?
This sounds like documentation overhead. It's actually faster than discovering these questions mid-implementation and having to refactor.
interface ButtonProps {
// Visual variants — map directly from Figma component properties
intent?: 'primary' | 'secondary' | 'ghost' | 'destructive';
size?: 'sm' | 'md' | 'lg';
// Content slots
children: React.ReactNode;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
// State
isLoading?: boolean;
disabled?: boolean;
// HTML passthrough
type?: 'button' | 'submit' | 'reset';
onClick?: () => void;
}When Figma component properties map directly to TypeScript props, the handoff is a translation, not an interpretation.
Responsive Implementation
Responsive design should be designed, not improvised in code. Before I implement any component, I want to see how it behaves at every breakpoint — not just the two extremes.
My breakpoint scale:
const breakpoints = {
sm: '640px', // Large phones
md: '768px', // Tablets
lg: '1024px', // Small laptops
xl: '1280px', // Desktops
'2xl': '1536px', // Large screens
};With Tailwind, responsive variants are explicit and co-located:
<div className="
flex flex-col /* mobile: stack */
md:flex-row /* tablet: side by side */
gap-4 md:gap-8 /* spacing scales up */
px-4 md:px-8 lg:px-16 /* padding scales up */
">The rule I follow: design for mobile first, then add complexity at larger breakpoints. It's easier to add than to subtract.
The QA Pass
Before any design handoff is considered complete, I do a QA pass against the implementation. My checklist:
Visual accuracy
- Spacing matches (use the browser inspector's box model)
- Typography matches (size, weight, line height, letter spacing)
- Colours match (not "close enough" — use the eyedropper)
- Border radius, shadow, opacity match
Behaviour
- All hover/focus states implemented
- Keyboard navigation works
- Touch targets are adequate on mobile
- Animations match the design (duration, easing)
Edge cases
- Long content doesn't break layout
- Empty states are handled
- Error states are handled
- Works with browser default font size increased to 200%
Accessibility
- Colour contrast passes WCAG AA
- Focus indicators are visible
- ARIA labels on icon-only buttons
- Logical reading order in the DOM
This is thorough because quality compounds. A component that's 90% right is a tax on every place it's used.2
The Mindset Shift
The biggest change in my practice has been thinking of Figma and code as two representations of the same thing, not as separate artifacts.
When I design, I'm thinking about how this will be implemented. When I implement, I'm thinking about whether the detail from the design is preserved. The translation is always lossy — the goal is to minimise the loss.
Design engineering isn't about being good at two things. It's about understanding the gap between them and caring enough to close it.
Footnotes
-
The concept of design tokens was popularised by Salesforce with their Lightning Design System in 2014. The W3C Design Tokens Community Group is currently working on a standard format for design token files (.json), which would allow tokens to be shared across tools without manual translation. ↩
-
This is the compound interest argument for quality. A button component that renders correctly in 90% of cases will produce visible inconsistencies in every product surface that uses it. Fix the component once, and you fix every instance simultaneously. The QA investment pays back immediately and continues paying indefinitely. ↩
More essays
View allRust • February 10, 2025
Outcome Engineering: The Discipline of Building What Actually Matters
Most engineers build features. Outcome engineers build results. The distinction sounds small but changes everything about how you approach a problem.
Open Source • January 15, 2025
The Design Engineer's Toolkit: Tools I Actually Use
A honest look at the tools, libraries, and workflows that make up my daily design engineering practice — what works, what doesn't, and why.
Typescript • January 28, 2025
Animation That Works: Making Interfaces Feel Alive
Most UI animation is decoration. The best animation is communication. Here's how I think about motion in product interfaces and what separates good animation from noise.