Most developers open a Figma file and immediately start building. I used to do the same. The result was always the same too — rebuilding things halfway through because the foundation wasn't thought through.
Now I follow a process. It has phases, each depending on the previous. Skipping any of them costs more time than doing them properly.
Reading the design — really reading it
Before touching code I spend time in the Figma file understanding what I'm actually building. Not the pages — the system behind them.
I look at the color styles first. Are they named semantically or are they raw hex values? I look at the typography — is there a real type scale or are heading sizes arbitrary per section? I look at spacing — is there a consistent rhythm or is everything eyeballed? I look at components — reusable and well-organized, or one-off frames?
This tells me two things: how well-considered the design is, and how much work the foundation phase will be. A well-organized Figma file cuts my build time significantly. A messy one means I extract the system myself before writing anything — which I do anyway, because that's the job.
I also note where I'll deviate from the design intentionally. Not ignore it — deviate. Sometimes a font size is too small for readability at a certain breakpoint, sometimes a spacing value doesn't translate to real content. I flag these, decide, and move on. The goal is a site that feels like the design, not a pixel-perfect export of it.
The design system comes first
Before any component gets built I translate the entire Figma design system into CSS custom properties. Colors, typography scale, spacing, border radius, transitions, shadows, buttons, forms — everything that repeats across the design gets a variable.
For colors I store HSL channels separately rather than full color values. This gives me the ability to modify alpha without touching opacity:
:root {
--color-dark: 0, 0%, 13%; /* #222222 */
--color-primary: 210, 100%, 43%; /* #006EDB */
--color-text: hsla(var(--color-dark), 0.7);
--color-text-muted: hsla(var(--color-dark), 0.45);
--color-headings: hsl(var(--color-dark));
}
Using opacity on an element affects everything inside it including children. Using hsla() on a color value is scoped — the transparency is on the color itself, not the element. This matters constantly in real UI work.
The same thinking applies to every other token. Radius is defined as a base value with calculated variants so everything scales proportionally. Transitions are defined once — speed and easing together — so animations feel consistent across the entire site. Spacing has a deliberate scale with semantic aliases for white space at different levels. Buttons and form inputs get their own variable groups so global changes propagate automatically.
This isn't over-engineering. When the client asks to soften the border radius across the site — one variable. When the primary color changes — one variable. The design system is the contract between the Figma file and the codebase, and it pays for itself on the first revision round.
Plugin architecture — theme stays light
The theme handles presentation only. Everything else lives in plugins. This is a rule I don't break.
For a typical project the structure looks like this:
wp-content/
themes/
project-theme/ ← layout, typography, color, spacing
plugins/
project-widgets/ ← all custom blocks or widgets
project-theme-options/ ← theme customizer or Redux options (optional)
The page builder decision — Gutenberg or Elementor — is made at the start of the project. Not both. One plugin holds all the custom blocks or widgets for that project. The theme has no opinion on which one was chosen.
The theme options plugin is project-specific and optional. Some clients need a full customizer panel for colors, fonts, and layout settings. Others don't. When it's needed it's a separate plugin — not baked into the theme — so it can be maintained or replaced independently.
Keeping the theme light has a practical benefit beyond clean architecture: it's fast. No unnecessary scripts, no plugin-style logic mixed into template files, no bloat that accumulates over time.
WooCommerce — hooks over template rewrites
When a project includes WooCommerce the rule is: use hooks, not template overrides, wherever possible.
Template overrides work by copying WooCommerce template files into the theme. They give you full control but they break silently on WooCommerce updates if the template structure changes. For a client who stays on a site for three years that's a real risk.
Hooks are the correct approach. WooCommerce has action and filter hooks throughout the checkout, cart, product pages, and account area. Most UI customizations can be achieved by removing default output and replacing it with custom output through those hooks:
// Remove default product title
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_title', 5 );
// Add custom product title
add_action( 'woocommerce_single_product_summary', 'project_custom_product_title', 5 );
function project_custom_product_title() {
printf(
'<h1 class="product-title">%s</h1>',
esc_html( get_the_title() )
);
}
When WooCommerce updates, hooks survive. Template overrides often don't. The few cases that genuinely require a template override are the exception, not the default approach.
Code quality — escaped, translatable, reusable
Every string that outputs to the frontend goes through a translation function. Every dynamic value that outputs to HTML is escaped. No exceptions.
printf(
'<a href="%1$s" class="btn btn-primary">%2$s</a>',
esc_url( get_permalink() ),
esc_html__( 'View project', 'project-theme' )
);
printf with indexed placeholders keeps the HTML readable and the escaping explicit. esc_url for URLs, esc_html__ for translatable strings, esc_attr for HTML attributes, wp_kses_post for content that allows limited HTML. The right function for the right context — not esc_html on everything as a shortcut.
This is the baseline. Sites go multilingual, codebases get handed to other developers, security audits happen. Code that's properly escaped and translatable from day one doesn't need to be retrofitted later.
Reusability follows the same principle. Template parts for repeated structures, shared component styles, reusable block patterns. If I build something more than once it becomes a component. The goal is a codebase where adding a new page means assembling existing pieces, not writing new ones.
Performance and SEO from the start
Neither is an afterthought. Both are built in from the beginning.
On the performance side: images are optimized and lazy loaded, scripts are deferred or loaded in the footer where possible, CSS is scoped rather than loaded globally. I don't install a plugin for something I can do in five lines of code. Every extra plugin is a performance hit and a future maintenance liability.
On the SEO side: heading hierarchy is correct in every template and block by default, semantic HTML throughout, Open Graph tags in the head, structured data where relevant. I use Yoast or Rank Math depending on the project but I never rely on them to fix a bad structure — the structure is right before the plugin is installed.
From local to staged
I develop locally and push to staging before the build is complete — typically at 60-70%. Showing an early staging gets feedback when it's cheap to act on. Waiting until everything is done means bigger surprises and more expensive revision rounds.
Staging runs on a VPS with Coolify handling deployment, SSL, and the reverse proxy. The client gets a link, reviews on a real environment, and gives feedback on something that behaves like the finished product — not a screenshot.
Before sharing I run the same checklist every time: forms tested end to end, images optimized, mobile checked on real devices, console clear, placeholder content replaced. Then a short Loom video — five minutes walking through the admin. How to edit a page, update the menu, add a post. That five minutes saves hours of back-and-forth support.
What this process actually solves
The process isn't about following steps — it's about moving decisions to where they cost the least.
Design system decisions made in phase one don't need to be made again during component building. Architecture decisions made before writing code mean there's never a debate about where something goes. Performance and SEO built in from the start means no scramble at the end. Escaped and translatable code from day one means the codebase is handoff-ready before anyone asks.
The result is a staged site that matches the design, performs well, holds up to future updates, and doesn't fall apart when someone else touches it.


