Skip to content
ExponaBLOG
WORDPRESS DEVELOPMENT & PLUGINS · DAY 026 / 100

Block themes and full-site editing: a step-by-step guide

Themes built entirely with blocks.

Expona Admin7 min read

Block themes and full-site editing: a step-by-step guide

WordPress 5.9 introduced block themes and full-site editing, fundamentally changing how we build themes. Instead of PHP templates and custom hooks, we're now designing entire sites using blocks—the same visual building blocks your clients use in the editor. This guide walks you through creating a modern block theme from the ground up, covering everything from configuration to advanced customization.

Understanding block themes vs traditional themes

Traditional WordPress themes rely on PHP files (header.php, footer.php, single.php) and hook systems. Block themes flip this approach. Your entire site—including headers, footers, archives, and singular pages—is built with block templates. The editing experience moves from a black box to full visual control.

When a client edits a block theme site, they're not just editing post content. They can modify the global layout, typography, and colors across templates. This democratizes design changes that previously required developer intervention.

To create a block theme, you need:

  • A theme.json file for global settings
  • A templates folder with block-based templates
  • A parts folder for reusable template parts
  • A style-variations folder for alternative designs (optional but recommended)
  • A functions.php for PHP hooks and theme support

theme.json — Colors, typography and spacing in one file

The theme.json file is your theme's single source of truth. It defines colors, fonts, spacing scales, and block default styles. Instead of scattering CSS across multiple files, you declare what's available globally and let WordPress handle the implementation.

Here's a practical example for an e-commerce site we built recently:

php
{
  "$schema": "https://schemas.wp.org/wp/6.3/theme.json",
  "version": 2,
  "settings": {
    "color": {
      "palette": [
        {
          "color": "#000000",
          "name": "Black",
          "slug": "black"
        },
        {
          "color": "#FFFFFF",
          "name": "White",
          "slug": "white"
        },
        {
          "color": "#0073AA",
          "name": "Primary Blue",
          "slug": "primary"
        },
        {
          "color": "#E74C3C",
          "name": "Accent Red",
          "slug": "accent"
        }
      ],
      "gradients": [
        {
          "gradient": "linear-gradient(to right, #0073AA, #005A87)",
          "name": "Blue Gradient",
          "slug": "blue-grad"
        }
      ]
    },
    "typography": {
      "fontFamilies": [
        {
          "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif",
          "name": "System",
          "slug": "system"
        },
        {
          "fontFamily": "'Georgia', serif",
          "name": "Georgia",
          "slug": "georgia"
        }
      ],
      "fontSizes": [
        {
          "size": "0.875rem",
          "name": "Small",
          "slug": "small"
        },
        {
          "size": "1rem",
          "name": "Base",
          "slug": "base"
        },
        {
          "size": "1.5rem",
          "name": "Large",
          "slug": "large"
        },
        {
          "size": "2.5rem",
          "name": "Extra Large",
          "slug": "x-large"
        }
      ]
    },
    "spacing": {
      "spacingScale": {
        "operator": "*",
        "increment": 1.5,
        "steps": 7,
        "mediumStep": 4,
        "unit": "rem"
      }
    }
  },
  "styles": {
    "color": {
      "background": "var(--wp--preset--color--white)",
      "text": "var(--wp--preset--color--black)"
    },
    "typography": {
      "fontFamily": "var(--wp--preset--font-family--system)",
      "fontSize": "var(--wp--preset--font-size--base)",
      "lineHeight": "1.6"
    },
    "elements": {
      "heading": {
        "typography": {
          "fontFamily": "var(--wp--preset--font-family--georgia)",
          "fontWeight": "700"
        }
      },
      "link": {
        "color": {
          "text": "var(--wp--preset--color--primary)"
        }
      }
    }
  }
}

This single file gives your theme consistency. When someone changes the primary color, it updates everywhere that uses var(--wp--preset--color--primary). No hunting through CSS files or PHP templates.

The spacing scale is particularly powerful. By setting an operator and increment, WordPress generates a proportional scale. This keeps your layout rhythm consistent without manually defining every gap and padding value.

Templates — HTML templates in the templates folder

Block templates are JSON-like files stored in your templates folder. Each template defines what blocks appear and in what order for different content types.

Create a basic file structure:

php
your-theme/
├── templates/
│   ├── index.html
│   ├── single.html
│   ├── archive.html
│   ├── search.html
│   └── 404.html
├── parts/
│   ├── header.html
│   ├── footer.html
│   └── sidebar.html
├── theme.json
└── functions.php

Here's a real index.html template for a blog homepage:

php
<!-- wp:template-part {"slug":"header"} /-->

<main class="wp-block-group">
  <!-- wp:query {"queryId":0,"query":{"perPage":10,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} -->
  <div class="wp-block-query">
    <!-- wp:post-template -->
    <div>
      <!-- wp:post-title {"isLink":true} /-->
      <!-- wp:post-excerpt {"moreText":"Read more"} /-->
      <!-- wp:post-date /-->
    </div>
    <!-- /wp:post-template -->

    <!-- wp:query-pagination -->
    <div>
      <!-- wp:query-pagination-previous /-->
      <!-- wp:query-pagination-numbers /-->
      <!-- wp:query-pagination-next /-->
    </div>
    <!-- /wp:query-pagination -->
  </div>
  <!-- /wp:query -->
</main>

<!-- wp:template-part {"slug":"footer"} /-->

This template uses the Query block to display posts with automatic pagination. When you edit the site as an admin, you can modify this template directly in the visual editor. No PHP knowledge required.

For custom post types or specific page needs, create additional templates. A single product page might look different from a post, so you'd create templates/single-product.html.

Template parts are reusable chunks of layout. Your header and footer appear on every page, so storing them as separate parts keeps them maintainable and prevents duplication.

Create parts/header.html:

php
<!-- wp:template-part-area {"area":"header"} -->
<header class="wp-block-group">
  <!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between","align":"center"}} -->
  <div class="wp-block-group">
    <!-- wp:site-title {"isLink":true} /-->
    
    <!-- wp:navigation {"ref":1,"layout":{"type":"flex"}} /-->
  </div>
  <!-- /wp:group -->
</header>
<!-- /wp:template-part-area -->

And parts/footer.html:

php
<!-- wp:template-part-area {"area":"footer"} -->
<footer class="wp-block-group">
  <!-- wp:group {"layout":{"type":"flex","justifyContent":"space-around"}} -->
  <div class="wp-block-group">
    <!-- wp:navigation {"ref":2} /-->
    <!-- wp:paragraph -->
    <p>&copy; 2024 Your Company. All rights reserved.</p>
    <!-- /wp:paragraph -->
  </div>
  <!-- /wp:group -->
</footer>
<!-- /wp:template-part-area -->

When you reference these parts in your templates using <!-- wp:template-part -->, they display consistently. If you need to update the header across your entire site, you edit parts/header.html once, and the change propagates everywhere.

We often create additional parts for common sections: parts/hero.html for banner areas, parts/testimonials.html for repeating components, and parts/sidebar.html for side navigation. This modular approach scales well as sites grow.

Style variations — Alternative styles users can switch

Style variations let users switch between predefined color schemes and typography combinations without editing code. This is powerful for agencies serving multiple brands or for giving clients design flexibility.

Create a styles folder and add JSON files for each variation:

php
your-theme/
├── styles/
│   ├── dark-mode.json
│   ├── professional.json
│   └── vibrant.json
└── theme.json

Here's styles/dark-mode.json:

php
{
  "version": 2,
  "title": "Dark Mode",
  "settings": {
    "color": {
      "palette": [
        {
          "color": "#1A1A1A",
          "name": "Background",
          "slug": "bg"
        },
        {
          "color": "#FFFFFF",
          "name": "Text",
          "slug": "text"
        },
        {
          "color": "#64B5F6",
          "name": "Primary Blue",
          "slug": "primary"
        }
      ]
    }
  },
  "styles": {
    "color": {
      "background": "var(--wp--preset--color--bg)",
      "text": "var(--wp--preset--color--text)"
    },
    "elements": {
      "link": {
        "color": {
          "text": "var(--wp--preset--color--primary)"
        }
      }
    }
  }
}

Users then access these variations through the Site Editor's "Styles" panel. A design agency might have variations for each client; an SaaS platform might offer light and dark modes. This turns WordPress into a true design system where users control aesthetics through the UI, not code.

Common mistakes

Not locking templates enough: Full-site editing is powerful, but you probably don't want clients deleting your header. Use the lock attribute in templates to prevent unauthorized changes:

php
<!-- wp:template-part {"slug":"header","lock":{"move":true,"remove":true}} /-->

Overcomplicating theme.json: Start minimal. Add spacing scales, color palettes, and typography as you discover what your site actually needs. Bloated theme.json files become maintenance nightmares.

Forgetting fallback templates: WordPress prioritizes templates by specificity. If you don't have a templates/single-product.html, it falls back to templates/single.html, then templates/index.html. Always provide a solid index template as your safety net.

Not testing across block editors: The Site Editor, Post Editor, and pattern directory all behave slightly differently. Test your blocks in each context.

Ignoring accessibility in block templates: Just because you're using blocks doesn't mean accessibility is automatic. Use proper heading hierarchies, ensure color contrast, and test with screen readers.

Key takeaways

  • theme.json is declarative: Define your design tokens once and reference them globally. This keeps sites consistent and maintainable.
  • Templates replace PHP files: Block templates are JSON-based and editable in the visual editor, lowering the barrier to site modifications.
  • Parts eliminate duplication: Store reusable sections like headers and footers as separate parts and reference them across templates.
  • Variations provide flexibility: Let clients switch between predefined design systems without touching code.
  • Locking prevents chaos: Use template locks to protect critical layouts while leaving appropriate areas editable.
  • Start simple: Build your theme incrementally. A solid index template and header/footer parts are often enough to start.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles