HandoffPro

Component Library Handoff: Design System to Code

·10 min read

TLDR

Component library handoff is the process of translating your Figma component library into a code component library (React, Vue, or Web Components) with 1:1 mapping, consistent naming conventions, and complete state documentation. Done right, it ensures designers and developers work from the same source of truth, eliminating back-and-forth about "how this should look."

Key takeaways:

  • Every Figma component should have exactly one corresponding code component (1:1 mapping)
  • Use prop-based variants (Button variant="primary") instead of creating separate components (PrimaryButton, SecondaryButton)
  • Document all component states (default, hover, focus, disabled, loading, error) to prevent implementation gaps
  • Automate handoff with tools like Figma plugins, Storybook, and design token pipelines
  • Component library handoff is the foundation of scalable design system handoff

What is Component Library Handoff?

Component library handoff is the specific process of taking a Figma component library—your buttons, inputs, cards, modals, navigation bars—and translating them into reusable code components. Unlike general design handoff, which might involve one-off page implementations, component library handoff creates a reusable system that both designers and developers reference for all future work.

The goal is simple: when a designer uses a Button component in Figma, a developer should be able to use the exact same Button component in code with identical visual output and behavior. If they diverge, you end up with "design drift" where the mockups and production build no longer match.

Component library handoff is a subset of design system handoff. While a full design system includes tokens, documentation, patterns, and governance, the component library is the concrete implementation layer—the actual UI building blocks.

This process matters most for teams with multiple designers and developers working across different features. Without it, every developer interprets "primary button" differently, leading to inconsistent UIs and higher maintenance costs.

1:1 Mapping Between Design and Code

The golden rule of component library handoff: one Figma component equals one code component.

This sounds obvious, but it's commonly violated in practice. Here's what NOT to do:

Anti-pattern:

// Bad: Separate components for each variant
<PrimaryButton />
<SecondaryButton />
<DangerButton />
<SmallPrimaryButton />
<LargePrimaryButton />

This approach leads to component explosion. If you have 3 variants and 3 sizes, you're maintaining 9 components instead of 1. Every change requires updating multiple files.

Correct pattern:

// Good: Single component with props
<Button variant="primary" size="medium" />
<Button variant="secondary" size="small" />
<Button variant="danger" size="large" />

Now you have one component to maintain. Props control the visual variations, just like Figma variants.

Mapping Figma Variants to Props

Here's how Figma variant properties map to React props:

Figma Concept React Equivalent Example
Component React component Button.tsx
Variant property Prop with union type variant: 'primary' | 'secondary'
Variant option Prop value variant="primary"
Boolean property Boolean prop disabled={true}
Instance JSX element <Button />

If your Figma Button has a "Variant" property with options Primary, Secondary, and Danger, your React component should have:

type ButtonVariant = 'primary' | 'secondary' | 'danger';
 
interface ButtonProps {
  variant: ButtonVariant;
  // ... other props
}

Naming Conventions

Consistent naming prevents confusion and makes the handoff predictable. Here are the conventions we recommend:

Component Names

Use PascalCase and match Figma exactly:

  • Figma: Button → Code: Button.tsx
  • Figma: TextInput → Code: TextInput.tsx
  • Figma: NavigationBar → Code: NavigationBar.tsx

Don't shorten or abbreviate unless Figma does:

  • Figma: Button → Code: Button.tsx (not Btn.tsx)
  • Figma: TextField → Code: TextField.tsx (not Input.tsx)

The only exception: if your team already has established code conventions (e.g., all components prefixed with Ui), apply those consistently. The key is predictability.

Prop Names

Match Figma variant property names when possible:

  • Figma property: "Variant" → Prop: variant
  • Figma property: "Size" → Prop: size
  • Figma property: "Has Icon" → Prop: hasIcon (camelCase in code)

For values, use lowercase with hyphens in Figma, camelCase in code:

  • Figma: primary-action → Code: 'primaryAction'
  • Figma: large → Code: 'large' (same)

Special Cases

Interactive states are boolean props:

  • disabled={true}
  • loading={true}
  • error={true}

Content is passed as children or dedicated props:

  • <Button>Click me</Button> (children)
  • <TextField label="Email" placeholder="you@example.com" /> (props)

Code Example: Props vs Variants

Here's a complete React Button component that mirrors a Figma component library structure.

Figma component setup:

  • Component name: Button
  • Variant property: Variant (Primary, Secondary, Danger)
  • Variant property: Size (Small, Medium, Large)
  • Boolean property: Disabled
  • Boolean property: Loading

React implementation:

// Button.tsx
import { ButtonHTMLAttributes, ReactNode } from 'react';
 
type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonSize = 'small' | 'medium' | 'large';
 
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  loading?: boolean;
  disabled?: boolean;
  children: ReactNode;
}
 
export function Button({
  variant = 'primary',
  size = 'medium',
  loading = false,
  disabled = false,
  children,
  className = '',
  ...props
}: ButtonProps) {
  const baseStyles = 'inline-flex items-center justify-center font-medium transition-colors rounded-md';
 
  const variantStyles = {
    primary: 'bg-blue-600 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
    danger: 'bg-red-600 hover:bg-red-700 text-white',
  };
 
  const sizeStyles = {
    small: 'px-3 py-1.5 text-sm',
    medium: 'px-4 py-2 text-base',
    large: 'px-6 py-3 text-lg',
  };
 
  const disabledStyles = 'opacity-50 cursor-not-allowed';
 
  const classes = [
    baseStyles,
    variantStyles[variant],
    sizeStyles[size],
    (disabled || loading) && disabledStyles,
    className,
  ].filter(Boolean).join(' ');
 
  return (
    <button
      className={classes}
      disabled={disabled || loading}
      {...props}
    >
      {loading ? (
        <>
          <span className="mr-2">
            <Spinner size={size} />
          </span>
          {children}
        </>
      ) : children}
    </button>
  );
}

Usage examples:

// Matches Figma: Button [Variant=Primary, Size=Medium]
<Button variant="primary" size="medium">
  Save Changes
</Button>
 
// Matches Figma: Button [Variant=Danger, Size=Large, Disabled=True]
<Button variant="danger" size="large" disabled>
  Delete Account
</Button>
 
// Matches Figma: Button [Variant=Secondary, Size=Small, Loading=True]
<Button variant="secondary" size="small" loading>
  Loading...
</Button>

This structure makes the relationship between Figma and code explicit. A designer looking at this code can immediately map it back to the Figma component.

Documenting Component States

Interactive components have multiple states that must be documented and implemented consistently. Missing states are a common source of bugs and design-code misalignment.

Essential states to document:

  1. Default – Component's initial appearance
  2. Hover – Mouse over (desktop only)
  3. Focus – Keyboard navigation or click focus
  4. Active – Currently being clicked/pressed
  5. Disabled – Interaction not allowed
  6. Loading – Async operation in progress
  7. Error – Validation failed or error occurred

Storybook for State Documentation

The best way to document component states is with Storybook. It creates a living component library where developers can see and interact with every state.

Here's a Storybook story showing all Button states:

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
// Default state
export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Primary Button',
  },
};
 
// All variants
export const AllVariants: Story = {
  render: () => (
    <div className="flex gap-4">
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
    </div>
  ),
};
 
// All sizes
export const AllSizes: Story = {
  render: () => (
    <div className="flex items-center gap-4">
      <Button size="small">Small</Button>
      <Button size="medium">Medium</Button>
      <Button size="large">Large</Button>
    </div>
  ),
};
 
// Interactive states
export const States: Story = {
  render: () => (
    <div className="flex flex-col gap-4">
      <Button>Default</Button>
      <Button disabled>Disabled</Button>
      <Button loading>Loading</Button>
    </div>
  ),
};
 
// State combinations
export const AllCombinations: Story = {
  render: () => (
    <div className="grid grid-cols-3 gap-4">
      {(['primary', 'secondary', 'danger'] as const).map(variant => (
        ['small', 'medium', 'large'] as const).map(size => (
          <div key={`${variant}-${size}`} className="flex flex-col gap-2">
            <Button variant={variant} size={size}>Normal</Button>
            <Button variant={variant} size={size} disabled>Disabled</Button>
            <Button variant={variant} size={size} loading>Loading</Button>
          </div>
        ))
      ))}
    </div>
  ),
};

This story file creates an interactive documentation page showing every possible Button state. Designers can review it to confirm the implementation matches Figma. Developers can reference it when using the component.

Pro tip: Use Storybook's autodocs feature to automatically generate prop documentation from TypeScript types. This keeps your documentation synchronized with your code.

Automating Component Handoff

Manual component handoff is time-consuming and error-prone. Here are tools that automate parts of the process:

Figma Plugins

Figma to Code plugins (like html.to.design, Quest, Anima) attempt to generate React components directly from Figma. Results are mixed—they work well for simple components but struggle with complex layouts and custom logic.

Better approach: Use plugins to extract design tokens (design tokens are the foundation), then manually build components that consume those tokens. This gives you clean, maintainable code rather than auto-generated spaghetti.

Design Token Pipelines

Tools like Style Dictionary transform design tokens from Figma into CSS variables, JavaScript constants, and platform-specific formats. Your components then reference these tokens instead of hard-coded values.

Example: Instead of bg-blue-600 in your Button, use bg-primary which maps to a token. When the primary color changes in Figma, you update the token file and all components automatically reflect the change.

See our Style Dictionary guide for complete setup instructions.

HandoffPro Approach

HandoffPro takes a different approach: it doesn't try to generate code automatically. Instead, it generates a structured JSONC brief that documents component specifications in a format AI coding assistants can understand.

For component library handoff, you'd:

  1. Screenshot your Figma component in all its variants
  2. Upload to HandoffPro
  3. Get a JSONC brief with extracted tokens, props, and state specifications
  4. Paste the brief into Cursor or Claude Code
  5. AI assistant generates the React component matching the spec

This gives you the speed of automation with the quality of hand-written code.

Component Library Handoff Checklist

Before you consider your component library handoff complete, verify:

  • Every Figma component has exactly one corresponding code component (1:1 mapping)
  • Component names match between Figma and code
  • All Figma variant properties are implemented as typed props
  • All component states (default, hover, focus, disabled, loading, error) are implemented
  • Components use design tokens for colors, spacing, and typography (not hard-coded values)
  • Storybook or similar documentation shows all component states and variants
  • TypeScript types prevent invalid prop combinations
  • Accessibility requirements (ARIA labels, keyboard navigation) are implemented
  • Components are tested across all browsers and devices
  • Design and engineering teams both reference the same component documentation

FAQ

Q: How do you map Figma components to React components?

A: Use 1:1 mapping where each Figma component corresponds to exactly one React component. Figma variants become component props, variant properties become prop types, and component instances become JSX elements. For example, a Figma Button with variants for size (small/medium/large) and variant (primary/secondary) maps to a React Button component with size and variant props.

Q: What naming convention should I use for design system components?

A: Use PascalCase for component names (Button, not button) and match Figma layer names exactly when possible. For variants, use prop-based naming: Button with variant='primary' instead of PrimaryButton. This keeps your component API flexible and prevents component explosion as you add more variants.

Q: How do you document all component states for developers?

A: Document each component with default, hover, focus, active, disabled, loading, and error states. Use tools like Storybook to create interactive documentation showing all state combinations. Include TypeScript types for props, default values, and examples of common usage patterns. This prevents developers from missing edge cases during implementation.

Stop Extracting Design Values Manually

Upload a Figma screenshot and get JSONC tokens + a Claude-ready prompt in 30 seconds.

Cookie Preferences

We use cookies for analytics and advertising. Essential cookies are always enabled. Learn more