Design-Driven Development: Build Types from Figma Quickly

Turn Figma mocks into TypeScript types and components, reduce refactors, and simplify Payload CMS integration.

·Matija Žiberna·
Design-Driven Development: Build Types from Figma Quickly

📚 Comprehensive Payload CMS Guides

Detailed Payload guides with field configuration examples, custom components, and workflow optimization tips to speed up your CMS development process.

No spam. Unsubscribe anytime.

Part 1 of the Design to Code series — A complete guide to building block systems with Payload CMS

I've rebuilt components more times than I'd like to admit. The pattern was always the same: start coding, realize halfway through that the data structure doesn't match what the design actually needed, refactor everything. It's exhausting.

Then I discovered a simple principle that changed how I approach frontend architecture: let design drive the data structure, not the other way around. This guide explains why that works and how it makes your life easier.

The Problem with Traditional Development

Most developers start like this:

  1. Guess at a data structure
  2. Build the backend/database
  3. Build the frontend UI
  4. Realize the UI doesn't match the design
  5. Refactor everything (repeat steps 1-4)

This approach treats design as an afterthought. The result? Hours of refactoring, inconsistent components, and code that doesn't match the original mockups.

The costs add up fast. A "simple button redesign" cascades into type changes, component rewrites, and data migration discussions. One bad decision early on haunts you for months.

The Solution: Design Comes First

Design-driven development flips the sequence:

  1. Look at Figma design
  2. Identify every data field the design needs
  3. Create types that match those fields exactly
  4. Build components using those types
  5. Everything works on day one—no refactoring needed

This works because Figma is already your specification. The design shows you exactly what data you need, what's required vs optional, what's displayed where, and how it relates to other elements.

Why guess when you can just look at the design?

How This Makes Your Life Easier

Let me show you five concrete ways this approach saves time and frustration.

1. No Guessing at Data Structure

You're building a feature card. Traditional approach:

interface Feature {
  id: string;
  name: string;
  // Wait... does it need description?
  // Does it need an image?
  // What about a button?
  // Is the icon required?
}

You end up making assumptions. Half of them are wrong. You discover the problems two days later when the designer says "it needs a link at the bottom."

Design-driven approach:

Open Figma. Look at the feature card design. You immediately see:

  • Icon (top left)
  • Title (below icon)
  • Description (below title)
  • Button/link (bottom)

Your type becomes obvious:

interface Feature {
  id: string;
  icon: string;           // ← Figma shows this
  title: string;          // ← Figma shows this
  description: string;    // ← Figma shows this
  cta: CTA;              // ← Figma shows this
}

Done. No guessing. Everything comes directly from the design.

2. Types Become Your Specification

When someone asks "what fields should this collection have?", you don't need meetings or debates. You look at Figma.

Your type IS your specification. It answers:

  • What data exists? (type fields)
  • What's required vs optional? (required fields vs ?:)
  • What format is it? (string, number, CTA, Media, etc.)
  • How does it relate to other data? (references other types)

Later, when you integrate with Payload CMS, you just follow your types. Payload config becomes a direct translation of your types. No surprises, no mismatches.

3. Components Write Themselves

Once you have the type, building the component is straightforward:

interface Feature {
  icon: string;
  title: string;
  description: string;
  cta: CTA;
}

// Component: just display what the type tells you to display
export function FeatureCard({ feature }: { feature: Feature }) {
  return (
    <Card>
      <Icon name={feature.icon} />  {/* It's a string → resolve to Lucide */}
      <h3>{feature.title}</h3>       {/* Display title */}
      <p>{feature.description}</p>   {/* Display description */}
      <Button>{feature.cta.label}</Button>  {/* Display CTA */}
    </Card>
  );
}

The type literally tells you what to render. There's no creativity needed—just follow the type. This is where the real speed comes from.

4. Example Data Becomes Your Documentation

Before integrating with Payload, you create example data. This becomes simultaneously:

  • Testing data (you can see components working immediately)
  • Documentation (new developers see how to use the type)
  • Proof of concept (the feature is already "working")
  • Copy-paste template (future developers copy this pattern)

One artifact serves four purposes. That's efficient.

5. Payload Integration is Trivial

The hardest part of CMS integration is usually the question: "What fields should we actually create in Payload?"

With design-driven development, you already have the answer. Your types ARE the answer.

Your manual type:

interface Industry {
  id: number;
  title: string;
  slug: string;
  description: string;
  image: Media;
  icon: string;
}

Payload config (just follow the type):

{
  slug: 'industries',
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text' },
    { name: 'description', type: 'textarea' },
    { name: 'image', type: 'upload' },
    { name: 'icon', type: 'text' },
  ]
}

No debates. No "should this be a string or number?" No mismatches. Just follow your specification.

The Magic: Zero Refactoring for Payload

This is the biggest payoff. Imagine building the entire frontend with custom types and example data. Then, months later, you integrate Payload CMS.

Before Payload:

import { industryExample } from '@/types/collections';

const industry = industryExample;

After Payload:

const industry = await getCollection('industries').findById(id);

Your component? Unchanged. Your types? Same imports, different source. Your entire codebase? Works exactly as before.

You're not building a prototype that needs rewriting. You're building the real thing incrementally. This is powerful.

Why This Works in Practice

Let's trace through a real scenario:

Day 1: Designer finishes "Featured Industries" mockup Day 2: You look at Figma, create the Industry type and FeaturedIndustries block type Day 3: You build the component using shadcn/ui and Lucide icons Day 4: You create example data and add it to the page. It looks perfect. Day 5-10: You build other features Month 2: You connect to Payload CMS. The integration takes one afternoon because your types and Payload config already match perfectly.

At no point did you say "we need to change the data structure." At no point did you refactor components because the fields changed. Design drove everything.

Key Principles of Design-Driven Development

These principles make the approach work:

  1. Design is Your Specification Stop guessing. Look at Figma. Design tells you exactly what you need.

  2. Types Mirror Design Your types should directly reflect what Figma shows. One-to-one mapping.

  3. Components are Dumb Components don't decide what data exists. They just display what types tell them to display.

  4. Reuse Global Types CTA, Image, Button—use them everywhere. Consistency is free.

  5. Example Data is Documentation Before Payload, example data IS your specification. Later, Payload provides the data.

  6. No Refactoring on CMS Integration Your code structure doesn't change. Only the data source changes.

When to Use This Approach

This approach works best when:

  • You're building a design system with multiple variations
  • You'll eventually integrate with a CMS like Payload
  • You want consistency across components
  • Design comes before implementation
  • You want minimal refactoring

It's less necessary if:

  • You're building a one-off page that won't scale
  • Design is very fluid and changes constantly
  • You're working with an existing, rigid database schema

For your project, it's exactly the right fit. You have Figma designs driving everything, and you plan to integrate Payload CMS. Design-driven development prevents the chaos.

The Real Benefit: Peace of Mind

Beyond the time savings and refactoring prevention, there's a mental benefit: you know you're building the right thing.

When you look at the design and say "this is what the design needs," you're making a decision based on evidence, not guessing. When you build the component, you're implementing a specification, not interpreting vague requirements. When you integrate Payload, you're confirming the contract was right all along.

That confidence compounds. You stop second-guessing yourself. You ship faster. Your code is more consistent. Your team understands the approach.

It's not magic. It's just having a clear specification before you code.

0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.