Back to Blog

Component-Driven Development: From Figma to Code

Component-Driven Development: From Figma to Code

Every project we ship starts in Figma. That sounds obvious, but the gap between “we have designs” and “we have production components” is where most teams lose weeks. Designers name things one way, developers interpret them another, and by the time you reach QA, the UI is a patchwork of inconsistencies that nobody can maintain.

We have shipped over a dozen products this way — including the full design system for MindHyv, which spans social feeds, booking flows, invoicing, and a digital storefront — and the workflow we have settled on eliminates most of that friction. This post walks through our Figma-to-code pipeline: how we name components, map variants, extract tokens, and hand off designs so that developers can build without guessing.

Start With a Shared Vocabulary

The single biggest source of waste in design-to-code translation is naming. A designer calls something a “card,” a developer calls it a “tile,” and the PM calls it a “listing.” Three names, one component, zero alignment.

Our rule is simple: the Figma component name is the code component name. No translation layer, no mapping document. If Figma calls it BookingCard, the Svelte component is BookingCard.svelte. If there is a variant called status=confirmed, the prop is status="confirmed".

This sounds trivial until you realize it requires discipline on the design side. We enforce a naming convention in Figma that mirrors how we structure code:

  • PascalCase for component names: BookingCard, InvoiceRow, ProfileAvatar
  • camelCase for variant properties: size, variant, isActive
  • Slash grouping for hierarchy: Forms/Input, Forms/Select, Forms/Textarea

The slash grouping in Figma maps directly to folder structure in code:

src/components/
  forms/
    Input.svelte
    Select.svelte
    Textarea.svelte
  booking/
    BookingCard.svelte
    BookingStatus.svelte

When we built the MindHyv design system, this convention saved us an estimated two weeks over the project timeline. The product has four distinct modules — social, booking, invoicing, and selling — each with its own set of components, but they all share a common base layer. Without strict naming, that shared layer would have fractured immediately.

UI components organized in a design system layout

Variant Mapping: Figma Properties to Component Props

Figma’s component properties feature is the bridge between design and code. Every variant property in Figma becomes a prop in your component. The key is making this mapping explicit and mechanical, not interpretive.

Here is how a typical Figma component’s variant structure maps to TypeScript:

// Figma component: Button
// Variant properties:
//   variant: primary | secondary | ghost
//   size: sm | md | lg
//   isDisabled: true | false
//   iconPosition: left | right | none

interface ButtonProps {
  variant: 'primary' | 'secondary' | 'ghost';
  size: 'sm' | 'md' | 'lg';
  isDisabled?: boolean;
  iconPosition?: 'left' | 'right' | 'none';
  children: string;
  onClick?: () => void;
}

Notice that the Figma properties translate almost one-to-one into a TypeScript interface. The only additions are runtime concerns like onClick and children, which do not exist in Figma.

We take this further by generating a variant matrix — a simple table that maps every Figma variant combination to its visual state. For a button with 3 variants, 3 sizes, and 2 disabled states, that is 18 combinations. We build all 18 in Storybook (or in our case, a simple Astro page that renders every variant) before integrating them into the app.

<!-- Button.svelte -->
<script lang="ts">
  export let variant: 'primary' | 'secondary' | 'ghost' = 'primary';
  export let size: 'sm' | 'md' | 'lg' = 'md';
  export let isDisabled: boolean = false;
  export let iconPosition: 'left' | 'right' | 'none' = 'none';
</script>

<button
  class="btn btn-{variant} btn-{size}"
  class:btn-disabled={isDisabled}
  disabled={isDisabled}
  on:click
>
  {#if iconPosition === 'left'}
    <slot name="icon" />
  {/if}
  <slot />
  {#if iconPosition === 'right'}
    <slot name="icon" />
  {/if}
</button>

The CSS classes follow the same naming: btn-primary, btn-sm, btn-disabled. No creative reinterpretation, no clever abstractions. Boring is good here.

Design Tokens: The Glue Between Figma and Tailwind

Design tokens are the values — colors, spacing, typography, shadows — that define a design system. In Figma, these live as styles and variables. In code, they live as CSS custom properties or Tailwind config values.

We extract tokens from Figma and codify them in our Tailwind configuration. Here is a simplified version of what the MindHyv token setup looks like:

// tailwind.config.ts
import type { Config } from 'tailwindcss';

const config: Config = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f7ff',
          100: '#e0effe',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
          900: '#1e3a5f',
        },
        surface: {
          primary: 'var(--surface-primary)',
          secondary: 'var(--surface-secondary)',
          elevated: 'var(--surface-elevated)',
        },
        content: {
          primary: 'var(--content-primary)',
          secondary: 'var(--content-secondary)',
          tertiary: 'var(--content-tertiary)',
        },
      },
      spacing: {
        'component-padding': 'var(--component-padding)',
        'section-gap': 'var(--section-gap)',
      },
      borderRadius: {
        card: 'var(--radius-card)',
        button: 'var(--radius-button)',
        input: 'var(--radius-input)',
      },
      fontSize: {
        'heading-1': ['2rem', { lineHeight: '1.2', fontWeight: '700' }],
        'heading-2': ['1.5rem', { lineHeight: '1.3', fontWeight: '600' }],
        'body-lg': ['1.125rem', { lineHeight: '1.6', fontWeight: '400' }],
        'body-md': ['1rem', { lineHeight: '1.6', fontWeight: '400' }],
        'caption': ['0.875rem', { lineHeight: '1.5', fontWeight: '400' }],
      },
    },
  },
};

export default config;

The CSS custom properties (var(--surface-primary), etc.) are defined in a global stylesheet and can be swapped for theming — dark mode, brand customization, whatever the project needs.

The critical discipline here is that no developer should be writing raw hex values or pixel measurements in component styles. Every value comes from the token system. If a value does not exist as a token, you either add it to the system (with design approval) or you are doing something wrong.

Wireframe and prototype sketches on a designer workspace

Atomic Design: The Organizational Principle

We loosely follow atomic design principles — atoms, molecules, organisms — but we do not get religious about it. The practical structure we use looks like this:

  • Primitives (atoms): Button, Input, Badge, Avatar, Icon
  • Composites (molecules): SearchBar (Input + Button), UserChip (Avatar + Text), FormField (Label + Input + Error)
  • Blocks (organisms): BookingCard, InvoiceTable, FeedPost, NavigationHeader

Each layer only imports from its own level or below. A Block can use Composites and Primitives. A Composite can use Primitives. A Primitive uses only tokens and base HTML.

This hierarchy maps directly to our Figma structure. The Figma file has pages named “Primitives,” “Composites,” and “Blocks,” and each component lives on the appropriate page.

src/components/
  primitives/
    Button.svelte
    Input.svelte
    Badge.svelte
    Avatar.svelte
  composites/
    SearchBar.svelte
    UserChip.svelte
    FormField.svelte
  blocks/
    BookingCard.svelte
    InvoiceTable.svelte
    FeedPost.svelte

When we built LancerSpace, this structure paid off immediately. The product has a CRM, proposal builder, invoice system, and project tracker — all sharing the same primitives but composing them into very different blocks. Without the layered structure, we would have ended up with duplicate button styles across every module.

Developer Handoff: What Actually Gets Communicated

We do not use Figma’s inspect panel as the primary handoff mechanism. It is useful for spot-checking values, but it is not how we communicate design intent. Instead, our handoff consists of three artifacts:

1. Annotated Figma frames. The designer adds notes directly on the canvas for anything that is not obvious from the design alone — interaction states, conditional logic, edge cases (what happens when a name is 80 characters long?), loading states, empty states.

2. A component inventory spreadsheet. For any new page or feature, we create a simple table listing every component on the page, whether it already exists in the system, and what props it needs. This takes 15 minutes to create and saves hours of developer questions.

ComponentStatusProps NeededNotes
BookingCardExistsstatus, date, providerAdd compact variant
TimeSlotPickerNewavailableSlots, onSelectComplex — needs interaction spec
ConfirmationModalExiststitle, body, onConfirmReuse from invoicing module

3. A token diff. If the feature introduces new tokens (a new color, a new spacing value), those are called out explicitly. No surprises in code review.

Handling the Messy Middle

The workflow above sounds clean, but real projects have rough edges. Here are the three most common problems we encounter and how we handle them:

Figma drift. The designer updates a component in Figma but does not tell anyone. We mitigate this with a weekly design sync — a 15-minute meeting where the designer walks through any changes to the system. If a component changed, a ticket gets created.

One-off designs. Sometimes a screen needs something that does not fit the system. A promotional banner with custom typography, a special onboarding flow with unique illustrations. We allow one-offs, but they live in a separate directory (src/components/one-off/) and they are explicitly tagged as non-system components. They do not get added to the design system unless they prove their value in at least two contexts.

Platform divergence. When building for both web and mobile (like we did with MindHyv and JustTheRip), the same component often needs platform-specific adjustments. A card that works on web might need different padding on mobile. We handle this at the token level — platform-specific token overrides — rather than creating separate components. The component stays the same; the tokens adapt.

Developer translating design mockups into production code

The Payoff

When this workflow is running well, a developer can look at a Figma frame and know exactly what to build without asking a single question. The component name tells them what file to create or modify. The variant properties tell them what props to define. The tokens tell them what values to use. The annotated frames tell them about edge cases.

On the Trackelio rebuild, we went from Figma designs to a fully functional feedback dashboard in under three weeks — a flow that covers survey creation, response collection, analytics, and team collaboration. The design system we built for it was small (around 30 components), but every component was named, tokenized, and documented using this workflow.

The investment in process pays for itself on the first project. By the second or third project, it is compounding — new products start with a functioning design system on day one.

What We Would Change

If we were starting fresh, we would invest earlier in automated token extraction. Right now, we manually copy token values from Figma into our Tailwind config. Tools like Figma’s Variables API and token export plugins have matured enough that this should be automated. We are working on that for 2026.

We would also standardize our component documentation earlier. We have historically relied on the Figma file as the source of truth, but as teams grow, having component docs in code (via JSDoc or a lightweight Storybook equivalent) becomes essential.

For a deeper look at how we structure our frontend projects, see our post on choosing between Astro and Next.js — the framework decision affects how components are organized and composed.

If you are building a product and want a team that takes design systems seriously from day one, reach out at hello@threshline.com.