Blog

How to Build a Custom WordPress Block Theme with theme.json: A Step-by-Step Guide

Learn to create a block theme from scratch using theme.json to control global styles, settings, and templates.

Summary

Building a custom WordPress block theme with Full Site Editing (FSE) can be daunting, but theme.json simplifies the process. This guide walks you through setting up a minimal block theme, defining global styles like colors and typography in theme.json, and creating custom templates using block markup. You'll learn to add block patterns and style variations, enabling a fully designable site without PHP template files. We'll cover common mistakes, such as mixing classic and block theme features, and show how to test your theme. By the end, you'll have a modern, extensible block theme ready for production. This approach leverages WordPress's latest capabilities for faster, more visual development.

Introduction

Full Site Editing (FSE) has revolutionized WordPress theme development, allowing you to design every part of your site visually. But many developers still rely on classic themes or struggle to create block themes from scratch. The key to unlocking FSE's power is theme.json—a configuration file that controls global styles, settings, and templates. This article provides a practical, step-by-step guide to building a custom block theme using theme.json. You'll learn to set up the folder structure, define color palettes and typography presets, create custom templates with block markup, and add style variations. By the end, you'll have a production-ready block theme that is easy to maintain and extend. Let's dive in and demystify FSE development.

For a broader look at how FSE is shaping the future of WordPress design, check out Gutenberg FSE: The Future of WordPress Design in 2026.

1. Setting Up a Minimal Block Theme

A block theme requires a minimum of two files: style.css and index.html. Let's create them.

Folder Structure

Create a folder called my-block-theme in your wp-content/themes/ directory. Inside, place:

  • style.css
  • index.html
  • (optional) theme.json

style.css

Even though block themes rely less on CSS, style.css is still required for theme identification:

/*
Theme Name: My Block Theme
Theme URI: https://example.com
Author: Your Name
Description: A custom block theme built with theme.json.
Version: 1.0
Requires at least: 5.9
Tested up to: 6.5
Requires PHP: 7.4
License: GPL v2 or later
Text Domain: my-block-theme
*/

The Requires at least: 5.9 line ensures the theme works with the FSE feature.

index.html

This is your fallback template. It must start with block markup. Create a simple layout:

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

<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
    <!-- wp:post-title /-->
    <!-- wp:post-content /-->
</div>
<!-- /wp:group -->

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

This uses block syntax to include header and footer template parts, and displays the post title and content.

Caveat: Block themes must not contain PHP template files like header.php or footer.php. Mixing classic and block templates will break FSE.

Activating the Theme

Upload the folder via FTP or zip install, and activate it. You should see a blank site with your minimal structure. Next, we'll style it with theme.json.

2. Mastering theme.json: Global Styles & Settings

theme.json centralizes all design controls. It lives in the root of your theme folder. Let's create one with color and typography settings.

Basic Structure

{
    "version": 2,
    "settings": {
        "color": {
            "palette": [
                {
                    "slug": "primary",
                    "color": "#007cba",
                    "name": "Primary"
                },
                {
                    "slug": "secondary",
                    "color": "#005a87",
                    "name": "Secondary"
                },
                {
                    "slug": "background",
                    "color": "#ffffff",
                    "name": "Background"
                }
            ],
            "gradients": []
        },
        "typography": {
            "fontFamilies": [
                {
                    "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
                    "slug": "system",
                    "name": "System"
                },
                {
                    "fontFamily": "'Georgia', serif",
                    "slug": "serif",
                    "name": "Serif"
                }
            ],
            "fontSizes": [
                {
                    "slug": "small",
                    "size": "14px",
                    "name": "Small"
                },
                {
                    "slug": "medium",
                    "size": "18px",
                    "name": "Medium"
                },
                {
                    "slug": "large",
                    "size": "24px",
                    "name": "Large"
                }
            ]
        },
        "spacing": {
            "padding": true,
            "margin": true,
            "units": ["px", "em", "rem"]
        }
    },
    "styles": {
        "color": {
            "background": "var(--wp--preset--color--background)",
            "text": "var(--wp--preset--color--primary)"
        },
        "typography": {
            "fontFamily": "var(--wp--preset--font-family--system)",
            "fontSize": "var(--wp--preset--font-size--medium)"
        }
    }
}

This defines a color palette, font families, font sizes, and global styles. The var() functions reference CSS custom properties automatically generated by WordPress. Users can then change these in the Site Editor.

Tip: Use CSS custom properties for consistency. Avoid hardcoding values in CSS files; let theme.json be the single source of truth.

3. Creating Custom Templates

Block templates are HTML files with block markup. They follow a hierarchy similar to classic template files. For example, create single.html for single posts:

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

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
    <!-- wp:post-title {"level":1} /-->
    <!-- wp:post-featured-image /-->
    <!-- wp:post-content /-->
    <!-- wp:post-navigation-link {"type":"previous"} /-->
    <!-- wp:post-navigation-link {"type":"next"} /-->
</main>
<!-- /wp:group -->

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

Similarly, create page.html, archive.html, etc. You can also create custom templates by adding a wp:post-template block inside a query loop.

For advanced customization, consider building custom blocks. Learn how in Beyond Basic Blocks: Crafting Custom Gutenberg Blocks for Enhanced WordPress Functionality.

Template Parts

Create a parts/ folder for reusable parts. For example, header.html:

<!-- wp:group {"tagName":"header","layout":{"type":"flex","flexWrap":"nowrap"}} -->
<header class="wp-block-group">
    <!-- wp:site-logo /-->
    <!-- wp:navigation {"themeLocation":"primary"} /-->
</header>
<!-- /wp:group -->

And footer.html:

<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">&copy; 2025 My Site</p>
<!-- /wp:paragraph -->

4. Adding Block Patterns and Style Variations

Block patterns are pre-designed layouts. Define them in a patterns/ folder. Create patterns/hero.php:

<?php
/**
 * Title: Hero
 * Slug: my-block-theme/hero
 * Categories: hero
 */
?>
<!-- wp:cover {"overlayColor":"primary","minHeight":50,"align":"full"} -->
<div class="wp-block-cover alignfull" style="min-height:50vh">
    <span aria-hidden="true" class="wp-block-cover__background has-primary-background-color"></span>
    <div class="wp-block-cover__inner-container">
        <!-- wp:heading {"textAlign":"center","level":1,"style":{"color":{"text":"#ffffff"}}} -->
        <h1 class="has-text-align-center has-text-color" style="color:#ffffff">Welcome</h1>
        <!-- /wp:heading -->
        <!-- wp:paragraph {"align":"center","textColor":"white"} -->
        <p class="has-text-align-center has-white-color">This is a custom hero section</p>
        <!-- /wp:paragraph -->
    </div>
</div>
<!-- /wp:cover -->

Style variations allow users to switch entire looks. Create styles/default.json and styles/dark.json that override theme settings.

5. Caveats and Best Practices

  • Performance: theme.json is cached. Clear the cache after changes. Use the wp_register_theme_json hook if needed.
  • Backward Compatibility: Block themes work only from WordPress 5.9. If your site must support older versions, consider a hybrid approach but avoid mixing template files.
  • Child Themes: Block themes can be used as parent themes for classic child themes, but not vice versa. Use template in style.css to declare a parent block theme.
  • Internationalization: Use the __() function inside PHP template parts for translatable strings. In block markup, specify text domain via the textDomain attribute.

For more on plugin development best practices, see Building Robust WordPress Plugins: A Practical Guide to Best Practices.

Conclusion

Building a custom block theme with theme.json empowers you to create a fully designable site without touching PHP. By following this guide, you've set up a minimal theme, defined global styles, created custom templates, and added patterns. Remember to test thoroughly, especially for edge cases like archive pages or custom post types. As WordPress evolves, block themes will become the standard. Start leveraging FSE today to streamline your workflow and offer clients unparalleled flexibility.

If you want to speed up theme creation even further, consider using a tool that generates a complete site from a description. For example, you can describe your desired layout and have a ready-to-use block theme in minutes.

Ready to take your block theme live? With a plain-text description, you can generate a fully functional landing page or website—no code required.

Sources (5)