From Figma to WordPress: My Complete Development Workflow

Quick Summary

  • Whether a Figma-built WordPress site survives depends on its content model: what can be edited, where, and what can't be broken.
  • Review the design for states, real content and in-between widths, then map repeated sections to options pages and tokens to CSS custom properties.
  • Build one template part per section that renders nothing when content is missing, and hand clients a map of where things are edited.

Months after a Figma to WordPress site launches, the client opens the admin alone to change one price. Either there is exactly one field for it, or there is a layout they can break.

That moment, not the launch-day screenshot, is what a Figma to WordPress build is for. Slicing the design into templates is the easy half. The half that decides whether the site survives is the content model: what can be edited, where, and what can’t be broken. Everything in my workflow is arranged around getting that right.

Phase 1: Review the design for what it doesn’t show

A mockup shows one ideal version of each screen. A website has to survive all the other versions. So the first pass through a Figma file isn’t admiration; it’s a builder’s review, looking for:

  • States. Hover and focus for everything clickable; empty, loading and error for anything dynamic; a “no results” view for search.
  • Real content. The longest heading, a testimonial that runs to a full paragraph, a project with no image.
  • Breakpoints. What happens between the drawn frames, and which elements reorder, stack or disappear on a phone.
  • Repetition. Sections that appear on several pages with identical content. These decide the content model in the next phase.

Real content is where designs crack. On my own site, a long review made a testimonial card look odd in the carousel. The fix was structural, not cosmetic: the carousel shows hand-picked testimonials, and each testimonial has an optional short version that the carousel uses instead of the full quote. The testimonials page still shows every quote in full.

Phase 2: Turn the Figma design into a WordPress content model

This is the step I’d never skip. For every part of the design, answer one question: where does this content live, and who changes it?

In the Figma fileIn WordPressWhere the client edits it
A section used on one page (hero, intro, closing call to action)An ACF Flexible Content layout with its own fieldsThe page, under Sections
A section repeated on several pages with the same content (pricing, FAQ, process)An options page, plus a layout with no fieldsTheme Settings, once
A collection with its own pages (portfolio projects, events)A custom post type and taxonomyIts own admin menu
A collection without pages (testimonials)A post type with no public URLsIts own list, ordered by dragging
Header, footer, logo, main buttonAn options page and WordPress menusTheme Settings and Menus
Long-form writingPost content in the editorThe post itself
Colors, type scale, spacingCSS custom properties in the themeNowhere: a developer changes them

The last row is deliberate. Tokens are design decisions, not content. A color picker for the brand color is how a designed site slowly stops being one.

The second row is the one I’d point to first. In my own theme, the page-level layout for a shared section has no fields at all, only a note saying the content is shared and where to edit it. The editor can place the FAQ on any page, but there is still only one FAQ. Services, process, pricing, FAQ and the testimonials heading all work this way, among 24 section layouts.

Save the field groups as ACF local JSON inside the theme, so the content model is version-controlled alongside the templates that read it. The longer case for this setup is in ACF + WordPress: why I still use it.

Phase 3: Tokens become custom properties

The Figma variables become CSS custom properties at the top of the stylesheet, with matching names, and everything below references names instead of values. Fluid values are worth looking for here, because one clamp() can replace a stack of breakpoint overrides. This comes straight from my site’s stylesheet, trimmed to the two properties that matter here:

CSS
:root {
  --container: 1160px;
  --gutter: clamp(24px, 6vw, 110px);
}

.container {
  max-width: var(--container);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

The gutter grows with the viewport between 24px and 110px, and no media query has to know about it. For the full translation, including light and dark modes, see using AI to turn Figma designs into production-ready websites.

Phase 4: Static first, or straight from Figma to the WordPress theme

There are two honest ways to build, and each has a cost.

Static first means building the design as plain HTML, CSS and JavaScript, getting it approved in a real browser, then converting it into theme templates. My own site went this way, although its redesign came as a 13-page static prototype rather than a Figma file. The theme kept the prototype’s CSS and JavaScript as they were, and WordPress-specific styles went into a separate small file. The gain is a pixel-true reference you can screenshot against, with front-end problems solved before WordPress enters the picture. The cost is a second pass, and a conversion step where markup gets split into template parts.

Straight to the theme means building each section directly as a template part. It’s faster when the site is small or the component set is mature and stable. The cost is that design problems and CMS problems arrive at the same time, with no static reference to compare against.

I lean static-first when a design carries a lot of motion or custom interaction, and straight-to-theme when it’s mostly content sections built from known components. Either way, each design section becomes one template part, and the template stays dumb: read the fields, escape the output, and render nothing when the essential content is missing.

PHP
<?php
/**
 * Section: closing call to action.
 * Rendered inside the Flexible Content loop, so get_sub_field() reads this row.
 */
$heading = get_sub_field( 'title' );
$button  = get_sub_field( 'button' ); // ACF link field, returned as an array

if ( ! $heading ) {
	return; // no heading, no section: never an empty box with a stray button
}
?>
<section class="cta">
	<h2 class="cta__title"><?php echo esc_html( $heading ); ?></h2>
	<?php if ( $button ) : ?>
		<a class="btn-pill" href="<?php echo esc_url( $button['url'] ); ?>"<?php echo $button['target'] ? ' target="_blank" rel="noopener"' : ''; ?>>
			<?php echo esc_html( $button['title'] ); ?>
		</a>
	<?php endif; ?>
</section>

The early return is an empty-state decision made in code. Where these files live, and why each one has a single job, is in how I structure a scalable WordPress project.

Phase 5: QA the parts nobody screenshots

A screenshot comparison catches layout drift. It misses most of what real visitors run into. The QA pass covers:

  • Breakpoints: the drawn widths, then a slow drag through everything in between.
  • Keyboard: tab through every page. Menus, accordions and carousels reachable, focus always visible, nothing trapped.
  • Performance: scripts load only where their section is used. On my site, the home hero’s script loads only on pages that contain that section.
  • Empty and broken content: remove an image, leave a field blank, paste a very long title. The page should degrade, not collapse.
  • Debug output: render every page with WP_DEBUG on and treat every notice as a bug.
  • URLs: if the build changes any, redirect them permanently. When my project pages moved from /projects/ to /project/, the old URLs got 301 redirects and the SEO plugin’s cached URLs were reset.

Phase 6: Hand over a map, not a login

A login isn’t a handover. The client needs to know where each thing lives, in plain words. The theme for my own site carries one in its README: a short table of where each kind of content is edited. For a site built this way, it looks like this:

  • Pages, under Sections: the sections on each page, in order. Add from the list, drag to reorder.
  • Theme Settings, Shared Sections: services, process, pricing, FAQ. Edit once, and every page that uses them updates.
  • Theme Settings, General: logo, header, footer, main button.
  • Portfolio and Testimonials: their own lists. Drag testimonials to set the order they appear in.

What the client can’t change belongs in the handover too: the layouts themselves. That limit is what keeps a custom build looking designed a year later. A team that needs new page layouts every week is a different project, and possibly a different tool.

This is the core of the WordPress work on my services page: I design the interface and build it too, so what gets approved is what goes live. The workflow, compressed:

  • Review the design for states, real content and in-between widths before building anything.
  • Decide where every piece of content lives before writing a template.
  • Keep tokens in code, not in fields.
  • One design section, one template part, and templates that render nothing when content is missing.
  • Hand over a map of where things are edited, not just a password.

For finished examples of Figma designs built as WordPress themes, Shopify stores and web apps, see my Figma to code projects.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More