What a Small-Site Design System Needs Before It Needs a Page Builder

Most people building a small WordPress site reach for a page builder right away. That skips something. A page builder hands you layout controls, sure—but it doesn’t hand you a design system. Without one, you get visual drift, spacing that makes no sense, and a maintenance headache that grows with every new landing page.

I picture a workshop. A page builder is the tool belt. A design system is the rulebook that says where the benches sit, how high the outlets go, and which fasteners you grab for which materials. You can build without that rulebook. You’ll just end up redoing the work later.

This article walks through what a small-site design system actually needs before you ever open a page builder. We’ll hit the core primitives, the WordPress-specific mechanics, and the kind of documentation that keeps the whole thing from falling apart over time.

Close-up of a designer working on a grid layout with sticky notes and color swatches on a desk
A design system starts with constraints, not components. Photo by Pexels.

The Primitive Layer: Tokens, Not Components

When someone says “design system,” a lot of people picture a component library: buttons, cards, hero sections. That’s the surface. Underneath sits the primitive layer—the stuff that governs what a builder actually spits out.

Primitives are the smallest, non-negotiable values. Colors, type scales, spacing steps, border radii, shadow presets. In WordPress these map straight to CSS custom properties and theme.json settings. Nail those first, and every block and every builder module pulls from the same set of constraints.

Color Tokens That Survive Context

A single hex value isn’t a token. A token is a named role: --color-surface, --color-text-primary, --color-border. You need a palette that holds up in light and dark contexts, with enough contrast to pass accessibility checks. WordPress block themes declare these in theme.json. When a page builder draws from theme.json, your colors stay locked in.

What I build first: a set of 8–12 functional color tokens. No more. Every new color I add later has to justify itself against an existing token. I run into too many small sites with 47 color variables, half of them collecting dust. That’s not a system; it’s a landfill.

Type Scales That Respect the Grid

Typography in a page builder goes sideways when people mix heading levels with random font sizes. A design system pins a type scale to the base spacing unit. If your base spacing is 8px, your line heights and margins should snap to multiples of 8. That keeps vertical rhythm intact when a builder drags a heading next to a paragraph.

I define my type scale in theme.json under settings.typography.fontSizes. Each size gets a slug, a value in rem, and a line-height multiplier. Then I map those slugs to heading levels and block presets. A builder module that respects theme.json will offer exactly those sizes and nothing else.

Spacing That Won’t Drift

Page builders love handing you sliders for padding and margin. Without a spacing scale, you wind up with 13px tops and 27px bottoms on the same card. Define a spacing scale—I lean on a 4px base step with stops at 4, 8, 12, 16, 24, 32, 48, 64, 96px—and enforce it through CSS custom properties or theme.json settings.spacing.spacingScale.

Once that scale is in place, a builder’s spacing controls turn into preset dropdowns instead of freeform inputs. That constraint is the whole point. It speeds up decisions and stops the slow creep of one-off values.

Designer sketching a spacing grid on paper with rulers and markers
Spacing scales are easier to sketch than to retrofit. Photo by Pexels.

Layout Primitives: Before the Builder’s Canvas

A page builder gives you a blank canvas. That’s the weakness. A design system gives you a constrained canvas with pre-defined zones. Before you crack open a builder, settle on the layout patterns that repeat across the site.

The Content Width and Its Gutters

Every small site needs one content width, one wide width, and gutter values that stay consistent from header to footer. In WordPress, these are theme-side settings: settings.layout.contentSize and wideSize. When a builder respects theme.json, its container blocks snap to these widths automatically.

I set the content width to something readable—often 680px for a text-heavy site, 800px for a portfolio. The wide width lands at 1200px or 1280px. Gutters run 24px on mobile and scale to 32px on desktop. Those numbers aren’t pulled from thin air; they come straight from the spacing scale.

Section Templates as Repeatable Zones

A small site might have 5–7 distinct section types: a hero, a feature grid, a testimonial row, a call-to-action, a footer. Each section type needs a name, a defined inner layout, and spacing rules for what sits above and below it. These aren’t builder templates yet—they’re paper sketches or block patterns with locked spacing.

I write these as block patterns in PHP or as synced patterns in the block editor. They rely on core blocks and theme-defined styles. Later, when I open a page builder, I can reproduce the same zones faster because the rules are already set.

WordPress Mechanics That Hold the System Together

A design system lives in files, not in a builder’s interface. For a WordPress site, the system has to hook into the platform’s architecture. Here are the mechanics I reach for.

theme.json as the Single Source of Truth

Since WordPress 5.8, theme.json has become the configuration backbone. It declares color palettes, typography, spacing, layout, and block defaults. A builder that reads theme.json inherits those settings. A builder that ignores it will fight you every step of the way.

I configure theme.json before installing a builder. I turn on appearanceTools: true to expose border, shadow, and spacing controls. I disable custom font sizes and custom colors at the block level to shut down drift before it starts. These settings become the guardrails the builder has to work inside.

Developer editing a JSON configuration file on a laptop with a dark theme
theme.json is where the system’s rules get encoded. Photo by Pexels.

CSS Custom Properties as the Runtime Layer

Tokens defined in theme.json turn into CSS custom properties on the front end. I lean on these hard. Every block style I write references var(--wp--preset--color--primary) instead of a hardcoded hex. If I change the token, every instance updates.

For a builder-created page, this means I can style builder modules by targeting the custom properties they output. Most builders let you slap a CSS class on a module. I write a small stylesheet that maps builder-specific classes to the theme’s custom properties. The builder becomes a layout tool, not a style tool.

Block Patterns as the Bridge

Block patterns are the native WordPress way to bundle a section template. Before I touch a page builder, I build the 5–7 core section patterns using core blocks. This pushes me to solve layout constraints with tools every builder can import or reproduce.

Patterns also pull double duty as documentation. When a client asks why a testimonial section has 48px of bottom padding, the pattern file shows the rule. If you hit structural snags early on—like a “Nothing Found” error on new pages—I covered that in What to Fix First When a New WordPress Site Says Nothing Found.

Documentation That Actually Works

A design system without documentation is a conversation you keep having. For a small site, I don’t write a 40-page spec. I write a single markdown file or a private Notion page with three sections.

The Token Reference

List every color token, its hex value, and its usage rule. List the type scale slugs. List the spacing stops. This section is pure reference—zero prose. When I build a new page, I keep this open in a side tab.

The Section Catalog

Each section type gets a screenshot, its pattern name, and its spacing rules. For example: “Hero section: full-width background, content width container inside, 96px padding top and bottom on desktop, 64px on mobile. Always followed by the first section with 0px top margin.”

The Decision Log

This is the most underrated piece. Every time I make a design decision that strays from the original rules, I log it. “Added a 12px border radius token because cards needed a softer variant for the testimonial section.” Six months later, I can see why a value exists and whether it still belongs.

When You Actually Open the Page Builder

Now the design system has primitives, layout zones, platform hooks, and documentation. The page builder becomes a tool for assembly, not discovery. You’re not picking colors in the builder; you’re picking from a preset. You’re not dragging spacing sliders; you’re choosing from the scale.

This shifts the builder’s role from a design sandbox to a layout accelerator. Sections snap together because the spacing rules are baked in. Typography stays consistent because the builder pulls from the theme’s type scale. The client can add a new page without mangling the visual language—constraints live at the system level, not the page level.

Which Builder Fits This Approach

Not all builders treat theme.json with respect. I look for builders that read global styles and expose them as presets, not as overrides. Builders that let me disable custom colors and font sizes per module jump to the front of the line. If a builder ignores my spacing scale and dangles a freeform slider, I pass.

The block editor itself, when configured with theme.json and custom block patterns, often replaces a third-party builder for small sites. It’s native, so there’s zero extra abstraction between the design system and the rendered page.

Common Pushback and Why I Ignore It

People tell me this is too much process for a five-page brochure site. I don’t buy it. The process I described takes a focused afternoon. The alternative—fixing inconsistent spacing, mismatched colors, and busted layouts across five pages—takes weeks spread over months. The upfront cost is low; the deferred cost stings.

Others say a page builder handles all of this. A builder handles layout; it doesn’t handle design governance. Governance is the system. Without it, the builder turns into a machine for creating variations, and variations are the enemy of a coherent site.

FAQ

Do I need a full design system for a one-page site?

You need the primitives. Even a single page has colors, type, and spacing. Defining those as tokens in theme.json takes minutes and stops the page from turning into a pile of one-off values that look different on every screen.

Can I build a design system with a page builder already installed?

Yes, but you’ll be retrofitting. Start by auditing the existing pages for color, type, and spacing values. Consolidate them into tokens, then apply those tokens to the builder’s global settings. Retrofitting is heavier than building the system first, but it pays off.

What if my page builder does not read theme.json?

You can still enforce the system through CSS custom properties and a shared stylesheet. Map the builder’s class names to your custom properties. It’s manual but effective. If the builder actively fights you—spewing inline styles that override your properties—consider a different builder.

How do I keep the system up to date over time?

Use the decision log. Every change to a token or a pattern gets a dated entry. Review the log quarterly. If a token hasn’t been used in six months, deprecate it. Small sites hoard dead tokens fast; pruning keeps the system lean.

How to Make the Block Editor Match the Frontend Closely Enough to Trust

Jonas Venn here. If you build WordPress sites the way I do—editor-first, lean, and without page builders—you know the ache of finishing a layout in the block editor only to hit preview and see a page that looks like it was interpreted by a different browser. Margins drift. Font sizes collapse. Spacer blocks misbehave. The trust you placed in the editor evaporates. Let’s fix that. We’re going to make the block editor output so close to the frontend that you stop checking preview and start shipping.

Developer working on a laptop with code and design layout visible on screen

Why the Block Editor Betrays Your Layout

The block editor runs inside an iframe with its own stylesheet. That isolation is intentional—it prevents your admin theme from mangling content—but it also means the editor doesn’t inherit your theme’s frontend CSS unless you explicitly hand it over. The result: a heading that looks bold and tight in the editor sits loose and thin on the live page. A group block with a background colour you chose from the palette renders a shade you didn’t pick.

Three things usually cause the mismatch:

  • Missing editor styles. Your theme’s style.css doesn’t load inside the editor, so typography, spacing, and colour rules are absent.
  • Block-specific overrides. Core blocks ship opinionated frontend styles that the editor doesn’t mirror, like .wp-block-cover padding or .wp-block-quote border width.
  • CSS specificity gaps. Even when editor styles exist, they often lose to inline styles or block support defaults that the frontend treats differently.

Understanding those three lets you close the gap methodically instead of patching it with random CSS hacks before every launch.

Step 1: Enqueue a Real Editor Stylesheet

Stop relying on add_theme_support( 'editor-styles' ) alone. Most themes use it to pull in a trimmed-down editor-style.css that ships with outdated rules. Instead, build a dedicated editor stylesheet that imports the exact same design tokens your frontend uses.

Register it with add_editor_style() and point it at a file that loads your CSS custom properties, your reset or normalizer, and your typography scale. If your frontend uses a Google Font, enqueue it inside the editor with the enqueue_block_editor_assets action so the font-family matches. No more fallback serif in the editor while the frontend shows Inter.

function jv_editor_styles() {
    add_editor_style( 'assets/css/editor.css' );
    wp_enqueue_style( 'jv-editor-font', 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap', array(), null );
}
add_action( 'after_setup_theme', 'jv_editor_styles' );
add_action( 'enqueue_block_editor_assets', function() {
    wp_enqueue_style( 'jv-editor-font' );
});

Inside editor.css, keep the same :root block that defines your spacing variables, colour palette, and fluid type scale. The block editor respects custom properties if they’re loaded before the blocks render. This single change eliminates the worst of the visual drift.

Step 2: Tame Block Support Output with theme.json

WordPress 5.8 gave us theme.json, and it is the most reliable way to control block spacing, colour, and typography across both editor and frontend from one source. When you define settings.spacing.spacingScale or styles.typography.fontSizes in theme.json, the block editor generates consistent classes and inline styles that the frontend picks up verbatim.

The trick is to avoid mixing theme.json settings with legacy CSS overrides. Pick one lane. If you set a global content width of 720px in theme.json‘s layout section, don’t also set max-width on .entry-content in your stylesheet. The double declaration creates a race condition where the editor shows one width and the frontend another depending on load order. Let theme.json own layout widths, and use your stylesheet only for things theme.json can’t reach, like link hover states or form elements.

Block Gap Consistency

Block gap is the invisible spacing between sibling blocks, and it’s the most common source of editor-frontend mismatch after typography. In theme.json, define styles.spacing.blockGap with a value from your spacing scale. The editor reads it. The frontend renders it via the .wp-site-blocks > * + * selector that core outputs. If you overwrite that selector in your CSS with a different value, you break the parity. Audit your theme’s stylesheet for any rule that targets .wp-site-blocks spacing and delete it. Trust the generator.

Close-up of a screen showing CSS code and a block editor layout side by side

Step 3: Register Block Styles That Travel

Custom block styles registered with register_block_style() are only half the story. The editor will show the style name and apply a class, but the frontend only gets the class if your theme’s CSS loads the corresponding rules. To make the preview trustworthy, write the CSS for your custom block style in the editor stylesheet as well.

Better yet, build block styles that use only the design tokens already available in the editor. A “framed” image style that adds a border should reference var(--wp--preset--color--border), not a hardcoded hex. When the colour palette shifts in theme.json, both sides update. That’s the kind of consistency that lets you build layouts without constantly clicking preview.

Step 4: Handle the Iframe Reality

The block editor iframe sandboxes content, which means global styles applied to the <body> or <html> elements won’t cascade into your blocks. If your frontend relies on a body class for background colour or font smoothing, the editor won’t see it. Use the editor-styles-wrapper class, which core applies to the iframe’s body, to replicate those global rules inside the editor stylesheet.

.editor-styles-wrapper {
    background-color: var(--wp--preset--color--background);
    -webkit-font-smoothing: antialiased;
    font-family: var(--wp--preset--font-family--primary);
}

Also watch for viewport-dependent units. The iframe width is narrower than the frontend viewport, so a vw-based font size or padding can look correct in the editor but blow out on a wide monitor. Stick with rem, em, or the fluid type scale from theme.json, which calculates based on container width, not viewport width.

Step 5: Audit Third-Party Blocks

A block from a plugin you installed six months ago is probably loading its own editor CSS and frontend CSS from different files. The developer may have tested neither against your theme. When a block consistently lies about its spacing, open the browser inspector on the editor iframe and on the frontend page side by side. Compare the computed box model. If the frontend shows extra padding from a plugin stylesheet, override it in your editor stylesheet with a selector scoped to that block’s class. You’re not fixing the plugin; you’re aligning the preview.

For a site that suddenly shows blank pages or broken layouts after a plugin update, the problem often starts with a missing template or a misrouted query. I wrote about diagnosing that exact scenario in What to Fix First When a New WordPress Site Says Nothing Found. The same methodical, layer-by-layer approach applies here: isolate the block, check its CSS source, and align the rules.

Step 6: Build a Visual Regression Habit

You don’t need a fancy tool. Before you publish a page, open two browser tabs: one with the block editor in fullscreen mode, one with the frontend preview. Resize both to the same width. Scroll through and look for alignment shifts, colour differences, or spacing anomalies. Make it part of your build checklist. After you do it ten times, you’ll start seeing patterns—a particular group block variant that always needs a margin reset, or a third-party button block that ignores your border-radius variable. Codify those fixes in your editor stylesheet so you never think about them again.

Two monitors displaying a design layout, one showing the editor and one showing the live site

The End State: Trust the Editor

When the block editor matches the frontend closely enough, something shifts in your workflow. You design in the editor. You make spacing decisions in the editor. You check responsive behaviour by resizing the editor frame, not by opening a staging URL on your phone. The preview button becomes a final confirmation, not a debugging step. That’s the bar. It’s reachable with a disciplined editor stylesheet, a well-configured theme.json, and a refusal to let third-party CSS run unexamined.

FAQ

Why does my heading font look different in the editor even after I added editor styles?

Two common causes. First, the editor iframe may not be loading the font file because the @font-face declaration is only in your frontend stylesheet. Enqueue the font separately on the enqueue_block_editor_assets hook so the iframe can download it. Second, a core block style or a plugin stylesheet might be setting a different font-weight or letter-spacing inside the editor. Inspect the computed styles in the iframe to find the override and mirror it in your editor stylesheet.

How do I stop full-width cover blocks from breaking out in the editor?

Cover blocks use an inner container that responds to the theme’s content width. If your theme.json sets a layout.contentSize but the editor stylesheet doesn’t apply it to the iframe’s body, the cover block will render at the iframe’s edge instead of the intended wide width. Add the .editor-styles-wrapper class to your editor stylesheet and set its max-width to match your layout.contentSize. Then let the block’s own alignment styles handle the rest.

Can I use CSS Grid inside the block editor and expect it to match the frontend?

Yes, but you need to be explicit. The block editor’s iframe doesn’t strip CSS Grid, but it does reset some box-model properties. If your grid layout uses a gap value that relies on a CSS custom property, make sure that property is defined in the editor stylesheet’s :root block. Also test the grid inside the group block container, because the group block can add wrapper divs that affect direct-child selectors. When in doubt, apply the grid class to the outermost block container.

Why do spacer blocks show different heights in the editor versus the frontend?

Spacer blocks use inline styles for height, which normally match. But if your theme or a plugin applies a margin or padding to all blocks through a global CSS rule, the spacer block inherits it on the frontend while the editor iframe may not. Inspect the spacer on the frontend for extra vertical space coming from a parent selector. Then either remove the global rule or scope it to exclude spacer blocks specifically.

Getting the Block Editor Close Enough to the Frontend That You Actually Trust It

If you build WordPress sites with the block editor, you know the feeling. You nudge a hero section into place, fuss with the spacing, dial in a careful type scale—then you hit preview. And it’s close, but off. Margins collapse differently. A group block’s background bleeds where it shouldn’t. The font weight that looked crisp in the editor sidebar now feels clunky against real server fonts. Trust goes out the window fast. You start second-guessing every layout call and reflexively opening the preview tab after every tiny tweak.

This isn’t just a minor annoyance. When the editor doesn’t faithfully represent what ships, building gets slow and uncertain. For a systems engineer, that uncertainty is just a technical problem with traceable causes and fixable parts. The block editor can match the frontend closely—close enough you stop checking. But you have to work through the rendering pipeline layer by layer.

Developer comparing block editor layout to frontend display on two monitors
Consistent rendering between editor and frontend starts with explicit style registration.

Why the Editor and Frontend Diverge

The mismatch isn’t one bug. It’s a stack of independent systems that are only loosely tied together. The block editor runs inside an iframe (since WordPress 5.9) that loads its own stylesheet context. The frontend loads theme styles, plugin styles, and often a completely different set of rendering rules. The block markup stays the same, but the CSS that hits it shifts between contexts.

Three main things cause the gap:

  • Editor stylesheets are incomplete. Most themes ship a thin editor-style.css that handles basic typography and block widths, but ignores block-specific spacing, responsive rules, or custom color palette overrides.
  • Frontend styles cascade into the editor unpredictably. The add_editor_style() function pulls in the theme’s main stylesheet, but often yanks in too much—resets, layout rules for the site header, or widget-area styles that collide with the editor chrome.
  • Block supports and theme.json are under-specified. Many themes define theme.json settings but don’t carry those settings through to the corresponding CSS custom properties in the editor iframe. Spacing presets, color palettes, and typography scales render differently—or not at all.

You can isolate and fix each one with a methodical approach. The aim isn’t pixel-perfect parity—that’s a trap—but structural parity: the same box model, the same type scale, the same spacing rhythm. When those match, the editor turns into a reliable construction tool.

Start with the Stylesheet That Ships to the Editor

The most direct lever is the editor stylesheet registered through add_editor_style(). Most themes call this function during setup and point to a compiled CSS file originally built for the frontend. That’s the first misstep. Frontend stylesheets contain rules scoped to the whole document—.site-header, .sidebar, .footer-widgets—with no counterpart inside the editor iframe. When those selectors resolve, they drag in unexpected margins, background colors, or fixed positioning that breaks the editor layout.

Build a dedicated editor stylesheet instead. Copy only the resets, typography, and block-level rules from your frontend CSS. Strip out anything that targets the site frame: navigation, footer, sidebars, archive loops. Keep what a block actually needs: headings, paragraphs, lists, quotes, buttons, group-block backgrounds, column gaps. Test by loading the editor on a page with a cover block, a columns block, and a group with a background color. If the spacing above and below those blocks mirrors the frontend within a few pixels, you’ve got the core right.

If your theme uses a CSS preprocessor, generate a separate editor build that imports only the partials you need. A typical SCSS setup might look like:

// editor-style.scss
@import 'reset';
@import 'typography';
@import 'block-spacing';
@import 'block-colors';
// Do not import layout, header, footer, widgets.

Then register it cleanly:

add_action( 'after_setup_theme', function() {
    add_editor_style( 'assets/css/editor-style.css' );
} );

This alone wipes out a big class of mismatches. The leftover gaps usually come from block-specific styles that only get enqueued on the frontend.

Close-up of CSS code editor with block editor stylesheet visible
A dedicated editor stylesheet strips out site-frame rules and keeps only what blocks render.

Align theme.json Output Across Contexts

WordPress 5.8 introduced theme.json as a central config file for block settings and styles. When a theme defines settings.spacing.spacingScale or styles.typography.fontSizes, the platform generates CSS custom properties that both the editor and frontend consume. In theory, that should guarantee parity. In practice, the output pipeline wobbles depending on how the theme layers its settings and whether it overrides core defaults correctly.

A common failure point: a theme defines custom font sizes in theme.json, but also loads a classic CSS file that hardcodes font-size values for h1–h6. The editor reads the theme.json values and renders headings at the declared scale. The frontend loads the hardcoded CSS, which overrides the custom properties with static pixel values. The editor shows a heading at clamp(2rem, 5vw, 3rem); the frontend shows 32px. The mismatch is immediate and jarring.

The fix is to let theme.json own the typography scale entirely. Remove any hardcoded heading sizes from your frontend stylesheet and lean on the CSS custom properties WordPress generates, like var(--wp--preset--font-size--large). If you need to style a heading differently in a specific block context, use the block’s own class selector and reference the preset variable—not a static value.

The same principle applies to spacing. If you use the block editor’s spacing controls (the padding and margin sliders in the sidebar), the values come out as inline styles. Those inline styles are identical in the editor and on the frontend. Trouble shows up when your theme’s frontend CSS adds margins to blocks that the editor doesn’t account for. Many themes apply a margin-bottom: 1.5rem to all block-level elements via a broad selector like .entry-content > *. The editor doesn’t have that rule, so blocks stack with only their intrinsic margins. The frontend adds extra space, throwing off the rhythm.

To close this gap, either replicate the block-spacing rules in your editor stylesheet or, better, remove broad block-level margins from the frontend and let the block spacing controls handle the rhythm. This shift moves spacing responsibility from the theme developer to the content builder—which is exactly where the block editor wants it.

Handle the Block Gap and Layout Support

WordPress 6.1 added block gap and layout support that generates CSS for flex and grid gaps inside row, stack, and group blocks. When it works, it’s clean: you set a block spacing value, and the platform spits out a gap property on the container. But the feature only activates if the theme declares support in theme.json under settings.layout and settings.spacing.blockGap. If the theme skips these declarations, the editor shows the gap control but the frontend renders nothing, or falls back to a default value that doesn’t match the editor preview.

Explicitly enable layout and block gap support:

{
  "settings": {
    "layout": {
      "contentSize": "800px",
      "wideSize": "1100px"
    },
    "spacing": {
      "blockGap": true
    }
  }
}

Then verify that the generated CSS appears in both the editor iframe and the frontend. In the browser inspector, look for rules targeting .wp-container-* classes that set gap and flex-wrap. If they show up on the frontend but not the editor, your theme likely disables the layout engine in the editor context unintentionally. Check for filters like block_editor_settings_all that might strip out layout styles.

This is also where you might discover a plugin injecting its own block styles only on the frontend. Many block-library plugins enqueue styles with the wp_enqueue_scripts hook but forget to hook into enqueue_block_editor_assets. If you depend on third-party blocks, audit their enqueue hooks and, if needed, write a small shim that loads the same stylesheet in the editor context. The block markup is the same; the styles should be too.

Test the Error State: When a Page Says Nothing Found

Trust in the editor also hinges on how reliably the frontend renders what you build. A common early-stage failure is the “Nothing Found” message on a freshly published page. It’s not always a permalink problem. Sometimes the page is published, the slug is correct, and the template is assigned, but the query still returns empty. When that happens, you lose faith that publishing will actually show your work. Before chasing rewrite rules, check the template hierarchy and query context. I’ve written about the first things to check in What to Fix First When a New WordPress Site Says Nothing Found. The fix is often a misassigned page template or a plugin that modifies pre_get_posts too aggressively.

Build a Visual Regression Routine

Once you’ve aligned the stylesheets, theme.json, and layout support, you need a way to catch regressions. Manual preview is unreliable. Instead, build a small set of test pages that exercise every block type you use: a page with a cover block and a nested group, a page with columns and custom spacing, a page with the query loop block showing a grid of posts. For each test page, grab a screenshot of the editor and a screenshot of the frontend at the same viewport width. Compare them side by side.

You don’t need automated tooling for this. A simple checklist and a folder of reference screenshots will do. Run through the checklist after any theme update, plugin installation, or WordPress major release. If a column block suddenly has a different gap on the frontend, you’ll catch it before it hits a live page. This is systems work: isolate the variables, test them explicitly, and keep the baseline documented.

Two screens showing editor and frontend side by side for visual comparison
Side-by-side comparison of editor and frontend at the same viewport catches regressions early.

When to Accept the Difference

Complete pixel parity is impossible and not worth chasing. Dynamic content—user avatars, comment counts, real post dates—doesn’t render in the editor. The editor also doesn’t execute server-side PHP from block render callbacks, so any block that relies on a dynamic render will show a placeholder or a static fallback. That’s expected. The trust you need is structural: does the box model hold? Do the colors map correctly? Is the type scale consistent? If yes, the editor is reliable enough to build in without constant previewing.

The block editor is a rendering surface, not a mockup tool. Treat it like one. Give it the same CSS context the frontend gets, strip out what doesn’t belong, and verify the output systematically. When those conditions are met, the editor becomes a tool you can trust—and you can spend your time building, not checking.

Frequently Asked Questions

Why do my custom font sizes look different in the editor?

This usually happens when your frontend stylesheet hardcodes heading sizes that override the theme.json-generated CSS custom properties. Remove the hardcoded values and let the presets control the scale, or make sure your editor stylesheet includes the same overrides so both contexts match.

Can I use the same stylesheet for editor and frontend?

You can, but it takes careful scoping. If you load the full frontend stylesheet into the editor with add_editor_style(), you’ll pull in site-frame rules that break the editor layout. A better route is to build a shared core partial and load that in both contexts, then keep layout-specific rules separate.

How do I fix block spacing mismatches without editing every block?

Enable block gap support in theme.json and remove broad block-level margin rules from your frontend CSS. When the block editor’s spacing controls are the single source of truth for margins and padding, the editor and frontend will render identical spacing without per-block adjustments.

What if a plugin’s block styles only load on the frontend?

Check the plugin’s enqueue hooks. If it only hooks into wp_enqueue_scripts, you can add a small custom function that enqueues the same stylesheet on enqueue_block_editor_assets. This makes sure the block renders with the same CSS in both contexts.

Getting the editor close enough to trust is a matter of closing the CSS gap systematically. Start with the editor stylesheet, align theme.json output, enable layout support, and verify with a repeatable test set. The payoff is a building experience where what you see is what ships.

The Difference Between a Template Problem and a Content Architecture Problem

The Difference Between a Template Problem and a Content Architecture Problem

When a WordPress site goes sideways, the symptoms tend to look the same. White screen. Missing post list. A 404 where there should be content. But the root cause usually falls into one of two buckets: a template problem, or a content architecture problem. Mix them up and you’ll burn hours chasing the wrong thread. This piece draws a line between them so you can fix the right layer the first time.

A developer debugging code on two monitors in a dark workspace

What a Template Problem Looks Like

A template problem sits in the presentation layer. Think of your theme—the PHP files, the template hierarchy, the WordPress loop. Your content is in the database, it’s published, the URL checks out, but the front end gives you a busted layout, a missing sidebar, or a raw shortcode instead of a gallery. That’s a template issue.

Common culprits: a child theme override that yanked a critical action hook, a page template assigned in the editor that never calls the_content(), or a WooCommerce archive that suddenly spits out a single-product grid instead of a category list. Template problems are structural failures in how WordPress assembles output. The data is fine. The rendering path is broken.

Quick Diagnosis Steps

  • Switch to a default theme like Twenty Twenty-Three. If the content shows up, the original theme is the problem.
  • Check the page template dropdown in the editor. A “Landing Page” template with no post loop will swallow your content whole.
  • Inspect the rendered HTML. A missing <article> wrapper or duplicate <h1> tags often points to a hook mismatch.

Template problems are local. They mess with how a single page, a custom post type archive, or a category renders. The fix is surgical: edit a template file, unhook a function, or swap a layout setting. The site’s data structure stays untouched.

A close-up of a WordPress theme file structure in a code editor

What a Content Architecture Problem Looks Like

A content architecture problem goes deeper. It’s about how your data is organized—post types, taxonomies, relationships, and the rules that connect them. When a blog archive shows zero posts even though you published five, or a filterable portfolio grid returns nothing for a valid category, you’ve moved past the template layer. The query itself is failing.

These failures often surface after a migration, a plugin update, or a bulk edit. Permalink settings might be correct, but a custom WP_Query in your template is pulling from a taxonomy that isn’t registered properly. Or a custom post type has publicly_queryable set to false in the registration arguments, making all its posts 404 by design. The template can be flawless—the architecture refuses to serve the data.

Where Architecture Breaks

  • Post type registration: A plugin registers a “Team” post type with has_archive disabled. The archive at /team returns a 404 because WordPress doesn’t recognize the slug.
  • Taxonomy misalignment: A portfolio template runs a WP_Query for project-category, but the taxonomy was registered as project-cat. Zero results, no error.
  • Rewrite rule collision: A page slug matches a custom post type slug. WordPress prioritizes the post type archive, hiding the page entirely.
  • Capability gaps: A custom role can’t read a post type because its capability was set to edit_posts instead of read. The loop runs but returns nothing for that user.

These aren’t rendering bugs. They’re data flow failures. The fix means flushing rewrite rules, adjusting registration arguments, or rethinking how content types connect. It’s a systems engineering task, not a theming one.

A developer planning a content model with sticky notes on a whiteboard

When Both Layers Overlap

Reality doesn’t always split cleanly. A “No posts found” message on a category archive could be a template problem—the theme’s archive.php might have a hardcoded query that ignores the current category. Or it could be an architecture problem—the category term was deleted from the database but the template still references it. The symptom is identical: an empty page.

In these cases, isolate the layer. First, check if the posts exist by hitting the REST API endpoint directly: /wp-json/wp/v2/posts?categories=5. If you get a JSON payload with posts, the architecture is solid. The template is the failure point. If the REST API returns an empty array, your content model is broken—posts aren’t associated with that category, or the category ID is wrong.

Another overlap happens with dynamic templates. A page builder like Bricks or Oxygen lets you design a template that queries a custom post type. If the builder’s query settings reference a post type that doesn’t exist, the template looks fine in the editor but renders nothing on the front end. The builder is innocent—the architecture changed underneath it. Always check the data registration before blaming the visual tool.

A Practical Debugging Workflow

When a client reports “the page is blank,” resist the urge to open the theme files first. Follow a layered path:

  1. Verify the content exists. Open the post or page in the admin. Confirm it’s published, not draft or private. Check the permalink slug matches the URL you’re visiting.
  2. Test with a default theme. If the content appears, you have a template problem. If not, the architecture is suspect.
  3. Inspect the main query. Add var_dump($wp_query->request) to the template’s header or use the Query Monitor plugin. Look at the SQL. Is it joining the right tables? Is the WHERE clause filtering on a meta key that no longer exists?
  4. Check rewrite rules. Visit Settings > Permalinks and just click Save. This flushes the rules. If a custom post type archive suddenly works, a rewrite rule was stale.
  5. Audit registration code. If you’re using a plugin like CPT UI or custom code, open the registration arguments. Look for public, publicly_queryable, has_archive, and rewrite settings. A single false in the wrong place cascades.

This sequence prevents the classic mistake: rebuilding a template that was never broken. I’ve seen developers spend days refactoring an archive template only to discover the custom post type had exclude_from_search set to true, which also blocked archives. The fix was a one-line change in a function, not a theme overhaul.

Design-Aware Systems Thinking

As a builder, you need to think like a systems engineer even when you’re in a design tool. Every template you build assumes a specific content model. If your portfolio grid expects a “Project” post type with a “Skills” taxonomy, you’ve created a dependency. Document those assumptions. When a site breaks six months later, you’ll know whether to fix the grid (template) or the taxonomy registration (architecture).

This matters especially on sites that use block themes and full-site editing. A Query Loop block is a template element, but its settings—post type, taxonomy filter, number of items—are essentially architecture decisions exposed in the UI. Changing the block’s post type selection from “Posts” to “Projects” doesn’t alter the registration; it shifts the query. If “Projects” isn’t registered, the block renders nothing. The block isn’t broken—the data layer is missing. Knowing the difference saves you from filing a bug report against the theme when the real fix is a missing register_post_type() call.

Sometimes the confusion starts right after a fresh install. You spin up WordPress, activate a theme, and hit the front page only to see a cryptic “Nothing Found” message. That moment can feel like a design failure, but it’s usually a content or configuration gap. I’ve written about the exact steps to diagnose that situation in What to Fix First When a New WordPress Site Says Nothing Found. The approach there aligns with this same principle: separate the data from the display before you change anything.

Real-World Scenarios and Their Fixes

Let’s ground this with a few cases from client work:

Case 1: The Vanishing Service Pages

A site had a custom “Services” post type with an archive at /services. The archive worked for months, then went 404 after a WordPress core update. The template used a standard archive-services.php file. Switching to Twenty Twenty-Three didn’t help—still a 404. The problem was architecture: the update had reset the has_archive argument to false because the registration code ran on init with a priority that conflicted with a plugin. Reprioritizing the hook and flushing rewrites fixed it. The template was never touched.

Case 2: The Blog That Wouldn’t List Posts

A blog index page showed “No posts found,” but individual posts were accessible via direct URL. The theme’s home.php had a custom WP_Query that only fetched posts with a specific meta value featured = yes. No posts had that meta key. The content existed, but the template’s query was overly restrictive. This was a template problem masked as a content issue. Removing the meta query restored the listing.

Case 3: The Filterable Portfolio That Went Empty

A JavaScript-powered portfolio grid used an AJAX endpoint to filter by taxonomy. After a taxonomy slug was renamed from project-type to project-category, the filters broke. The AJAX handler still referenced the old slug. The template (the grid markup) was fine, but the architecture (the taxonomy registration) had changed without updating the dependent code. This was a hybrid failure, but the fix was architectural: update the handler and flush rewrites.

Building with Intent

The line between template and architecture isn’t academic. It’s the difference between fixing a symptom and solving the cause. A template problem is a carpenter fixing a crooked door. An architecture problem is realizing the wall was framed wrong. Both use tools, but the skill set and the time investment are worlds apart.

Next time you open a site to debug, ask: is the data available and correctly structured? If yes, the template is your target. If no, step back from the theme files and look at how you’ve modeled the content. Build that reflex, and you’ll waste fewer hours chasing ghosts in the template hierarchy.

Frequently Asked Questions

How do I know if a blank page is a template or architecture failure?

First, confirm the content is published and public. Then switch to a default WordPress theme. If the content appears, the original theme’s template is the issue. If the content still doesn’t appear, the problem is likely in the content architecture—post type registration, rewrite rules, or query parameters.

Can a plugin cause a content architecture problem?

Absolutely. Plugins that register custom post types or taxonomies can introduce architecture issues if their code has errors, conflicts with the theme, or uses non-standard arguments. Deactivating the plugin that registered the content type and re-checking the site is a direct test.

Why does flushing permalinks fix some issues?

WordPress stores rewrite rules in the database. When a custom post type or taxonomy is registered, its rules are added, but sometimes they get stale or aren’t written correctly. Visiting Settings > Permalinks and clicking Save forces WordPress to regenerate the rules, often restoring access to archives and single posts.

Is a missing sidebar a template or architecture problem?

Almost always a template problem. Sidebars are registered by the theme or a plugin, and their display is controlled by template files. If a sidebar isn’t showing, check the template’s get_sidebar() call and the widget area registration. The content architecture doesn’t govern sidebars.

How to Audit a WordPress Theme Before You Customize It

You grab a fresh theme, crack open the Customizer, and start tweaking—I see this all the time, and it usually ends in a mess. You’re inheriting someone else’s markup choices, their script-loading habits, their half-baked performance fixes, all before you write a single line of your own CSS. A rushed start means you’ll burn hours later picking apart conflicts you could have spotted in the first twenty minutes. I treat every theme like I’m doing a pre-build walkthrough. It’s the same routine I use on jooomshaper.com, and it keeps the site snappy, the block editor feeling solid, and the design system from turning into spaghetti.

Start with the Architecture: Files, Templates, and Hierarchy

Before you do anything else, open that theme folder and just look at how it’s organized. A clean theme lays things out so you can tell at a glance where the templates live, where the assets sit, and where the includes get pulled in. I want to see header.php, footer.php, functions.php, and a template-parts directory if the thing is supposed to play nice with blocks. If I spot a functions.php that’s 800 lines long and loading three dozen unrelated scripts, I know right then I’ve found the first thing to fix.

Developer reviewing code structure on a monitor

Check how much of the template hierarchy the theme actually covers. Does it bring a single.php, an archive.php, and a 404.php? No 404.php is a small disaster waiting to happen—you’ll be patching that immediately. On a site with broken routing, you might find yourself staring at a “Nothing Found” message right from the start. I wrote up how I deal with that exact headache here: What to Fix First When a New WordPress Site Says Nothing Found.

Dig into functions.php and see how the theme registers its menus, widget areas, and theme support. I expect clear declarations: add_theme_support('post-thumbnails'), add_theme_support('html5', array('search-form', 'comment-form')). Fuzzy or absent support flags usually mean the theme is old or built by someone who didn’t quite get how the current WordPress stack works. Also, keep an eye out for hardcoded URLs. A direct link to a Google Fonts stylesheet instead of a proper enqueue? Yeah, I’m going to strip that and self-host the fonts before I do anything else.

Audit the CSS and JavaScript Loading Strategy

Performance leaks almost always start in how the assets get queued up. Fire up the browser’s DevTools, flip to the Network tab, and reload a single page. Filter down to CSS and JS. Count the requests. A theme that’s put together well loads one main stylesheet and maybe one or two small JavaScript files on a typical page. If you’re looking at 15 CSS files and 10 JS bundles, the developer went for feature overload instead of sane loading.

Network tab in browser developer tools showing loaded resources

Go back to functions.php and hunt for wp_enqueue_style and wp_enqueue_script. I’m looking for three signals: conditional loading, dependency declarations, and version parameters. Scripts should only load on the pages that actually need them. A contact form script that fires on every page is just wasted bandwidth. Dependencies need to be spelled out properly—if a script needs jQuery, list it as a dependency so WordPress sorts the order. Version parameters should lean on the theme’s version constant or filemtime() for cache busting. Hardcoded versions turn into a maintenance headache fast.

Watch for inline scripts. A stray