The Gap That Kills Products Quietly
You’ve designed a beautiful, well-considered interface. The Figma file is clean, the components are well-structured, the prototypes feel polished. You hand it off to engineering.
Six weeks later, the built product looks… close. But not quite right. The spacing is slightly off. The hover states are missing on half the interactive elements. The mobile breakpoints weren’t implemented the way you specified. The loading state you designed was quietly dropped because "there wasn’t time."
This isn’t a failure of engineering. It’s a failure of handoff.
After 20+ years bridging design and development — including time spent writing HTML, CSS, and JavaScript myself — I’ve developed a workflow that dramatically reduces the gap between what’s designed and what ships. This article covers it in full.
Why Handoff Fails
Before the solutions, the diagnosis:
Designers don’t speak the implementation language. If a designer specifies font-size: 16px; line-height: 24px; font-weight: 500 as a static value rather than referencing a token (text.body.md), the engineer has no way to know this should map to a reusable style — so they hardcode it. Do this 40 times across a product and you have 40 inconsistent hardcoded values.
Engineers don’t have context for design decisions. A developer who doesn’t know why a component is designed a certain way will make different implementation decisions than one who does. The comment "this needs to feel premium, so the transition timing is deliberate — don’t shorten it" is worth more than the spec saying transition: all 200ms ease-in-out.
Specs are static but products are dynamic. A Figma frame shows one state. A real interface has dozens: loading, empty, error, partial data, edge-case content lengths, dark mode, reduced motion, focus states. If the spec doesn’t cover them, engineers either guess or skip them.
Handoff is treated as an event, not a process. "Handoff" implies a transfer — design throws it over the wall and steps back. In practice, the best design-development relationships are continuous: designers stay involved through implementation, answer questions in real time, and review builds before they ship.
The Foundation: CSS Custom Properties as the Source of Truth
If you use a design system, your tokens should drive both Figma variables and CSS custom properties — and they should use the same names.
Here’s a token architecture I use that maps cleanly between Figma and code:
/* Colour tokens */
:root {
--color-brand-primary: #00d4aa;
--color-brand-secondary: #7c6ef4;
--color-text-primary: #1a1a2e;
--color-text-secondary: #6b6b8a;
--color-text-inverse: #ffffff;
--color-bg-default: #ffffff;
--color-bg-surface: #f5f5f7;
--color-bg-elevated: #ffffff;
--color-border-default: rgba(0, 0, 0, 0.08);
--color-border-strong: rgba(0, 0, 0, 0.2);
--color-status-success: #22c55e;
--color-status-warning: #f59e0b;
--color-status-error: #ef4444;
--color-status-info: #3b82f6;
}
/* Dark mode overrides */
@media (prefers-color-scheme: dark) {
:root {
--color-text-primary: #e8e8f0;
--color-text-secondary: #8888aa;
--color-bg-default: #0a0a1a;
--color-bg-surface: #12122a;
--color-border-default: rgba(255, 255, 255, 0.08);
}
}
/* Spacing tokens */
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--space-7: 48px;
--space-8: 64px;
}
/* Typography tokens */
:root {
--text-display-xl: 800 48px/1.1 var(--font-sans);
--text-heading-lg: 700 32px/1.2 var(--font-sans);
--text-heading-md: 700 24px/1.3 var(--font-sans);
--text-body-lg: 400 18px/1.7 var(--font-sans);
--text-body-md: 400 16px/1.7 var(--font-sans);
--text-body-sm: 400 14px/1.6 var(--font-sans);
--text-label: 600 12px/1.4 var(--font-sans);
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--letter-spacing-tight: -0.03em;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.05em;
--letter-spacing-wider: 0.1em;
}
/* Radius tokens */
:root {
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
}
/* Shadow tokens */
:root {
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
}
When Figma variables use the same names as CSS custom properties, the mapping is unambiguous. An engineer reading --color-text-secondary in a spec knows exactly what to write in code.
Component Specification That Engineers Actually Use
A good component spec answers these questions before an engineer has to ask:
1. What are all the visual states?
Default, hover, focus, active, disabled, loading, error, success — and for form elements, also empty, filled, and validated.
2. What are the content edge cases?
What happens if the button label is 30 characters? What happens if the card has no image? What happens if the user’s name is 60 characters long and breaks the layout?
3. What are the interaction timings?
Hover transitions, animation durations, easing curves. Be specific: transition: background-color 150ms ease-out is a spec. "Smooth transition" is not.
4. What’s the accessible behaviour?
Focus ring style. Keyboard interaction pattern. ARIA roles and attributes. Screen reader label. This should be in the design spec, not left for engineering to figure out.
5. What are the responsive rules?
Which properties change at which breakpoints? Does the component reflow, stack, or hide on mobile?
I use a Figma annotation plugin (currently Figma’s built-in Dev Mode) to attach these notes directly to components. Engineers can hover over any element and see the spec inline.
The Naming Convention Agreement
One of the most impactful conversations you can have with your engineering team takes five minutes and saves weeks:
Agree on a naming convention and use it everywhere.
My preferred convention for component names: ComponentName--variant__element
Button--primary
Button--secondary
Button--ghost
Button--destructive
Button--primary__icon
Button--primary__label
Card
Card--featured
Card--compact
Card__header
Card__body
Card__footer
Card__image
In Figma, layer names follow this convention. In CSS/SCSS, class names follow this convention. In React/Vue, component names follow this convention.
When a designer says "the Card--featured needs a stronger border on hover", every engineer knows exactly what they’re talking about and where to find it in the codebase.
The Handoff Checklist
Before I hand off any screen or component to engineering, I run through this list:
Design completeness:
- [ ] All interactive states designed (hover, focus, active, disabled, loading, error)
- [ ] Empty states designed for all dynamic content areas
- [ ] Edge cases documented (max content length, missing images, error states)
- [ ] Responsive behaviour specified for all breakpoints
- [ ] Dark mode variants provided (if applicable)
- [ ] Reduced motion variants noted (if animations are significant)
Specification clarity:
- [ ] All spacing uses token references, not arbitrary values
- [ ] All colours reference tokens, not hex values
- [ ] Typography uses defined text style names
- [ ] Component names match agreed naming convention
- [ ] Interaction timings are specified (not just described)
Accessibility:
- [ ] Touch/click targets meet minimum size (44×44px)
- [ ] Focus states are visible and designed
- [ ] Colour contrast ratios meet WCAG AA minimum (4.5:1 for normal text)
- [ ] ARIA roles and labels noted for complex components
- [ ] Keyboard navigation patterns documented
Context:
- [ ] Design intent notes added for non-obvious decisions
- [ ] Links to user research or usability test findings that informed the design
- [ ] Known limitations or constraints noted
Staying Involved Through Implementation
The handoff moment is not the end of the designer’s job. It’s the beginning of the build review phase.
How I stay involved without becoming a blocker:
Weekly build reviews. I look at the built implementation against the spec every week during the implementation sprint. Not to nitpick — to catch systemic issues early. Spacing that’s consistently 4px off across every component suggests a misunderstood grid, not a one-off error.
A dedicated Slack channel for design questions. Engineers can ask questions without waiting for a meeting. I can answer asynchronously. Both sides move faster.
A "design debt" log. Not everything will be implemented perfectly in the first sprint. I keep a running log of known gaps — not to be precious about them, but to have a prioritised list for the next design-dedicated sprint.
Celebrate good implementation. When engineering ships something that matches the intent exactly — especially the subtle details like transition timing or typography scale — say so. The positive signal reinforces the value of the spec discipline.
Tools I Use in 2026
- Figma Dev Mode — inline specs attached to components; engineers can inspect and export values without leaving their workflow
- Storybook — living component documentation that bridges design and code; I review Storybook stories during implementation to verify component states match specs
- CSS custom properties — the token layer, as described above
- Linear — design tasks tracked alongside engineering tasks; no "design silo" and "engineering silo"
- Loom — async video walkthroughs for complex components; faster than writing a spec document, more precise than a Figma annotation
Closing Thoughts
The best design-to-code handoffs I’ve been part of felt less like handoffs and more like continuous collaboration. The word "handoff" implies a baton pass — one person stops, another starts. The reality of good product teams is more like relay swimming: you’re both in the water for a while, and the transition is smooth because you’ve been swimming in sync.
That requires investment on both sides: designers who understand how their decisions will be implemented, and engineers who understand why design decisions matter. Build that relationship and the gap closes itself.
I offer design-to-code review and consultancy for teams struggling with implementation fidelity — from token architecture to component specification to build review workflows.