How to Create a WordPress Block Theme
A working block theme needs four files. Build it from scratch, define the design system in theme.json, then let the Site Editor and the Create Block Theme plugin do the repetitive work.
The shortest path to a working theme: create four files, activate it, then build the rest visually in the Site Editor and export the result back to files. Writing every template by hand first is slower and rarely produces better markup.
If you have not met the model yet, read what is a WordPress block theme before starting.
The minimum file structure
wp-content/themes/my-theme/
├── style.css
├── theme.json
├── templates/
│ └── index.html
├── parts/
│ ├── header.html
│ └── footer.html
└── screenshot.png
style.css — only the header comment is required:
/*
Theme Name: My Theme
Theme URI: https://example.com/my-theme
Author: Your Name
Description: A small block theme.
Version: 1.0.0
Requires at least: 6.4
Tested up to: 6.7
Requires PHP: 8.0
License: GNU General Public License v2 or later
Text Domain: my-theme
*/
templates/index.html — the fallback template every other one inherits from:
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:query {"queryId":1,"query":{"inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true,"level":2} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
That block-comment syntax is what the editor writes for you. You rarely type it by hand beyond the first template.
theme.json: the design system
This file decides what the theme looks like and what editors are allowed to change.
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"appearanceTools": true,
"layout": {
"contentSize": "680px",
"wideSize": "1100px"
},
"color": {
"custom": false,
"palette": [
{ "slug": "base", "color": "#ffffff", "name": "Base" },
{ "slug": "contrast", "color": "#111418", "name": "Contrast" },
{ "slug": "accent", "color": "#1f6feb", "name": "Accent" }
]
},
"typography": {
"fluid": true,
"fontSizes": [
{ "slug": "small", "size": "0.9rem", "name": "Small" },
{ "slug": "medium", "size": "1rem", "name": "Medium" },
{ "slug": "large", "size": "1.5rem", "name": "Large" }
]
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": { "text": "var(--wp--preset--color--accent)" }
}
}
}
}
Two things worth internalising:
settingscontrols what is available;stylessets the defaults. Setting"custom": falseon colour removes the arbitrary colour picker, which is how you keep a site on-palette.- Presets become CSS variables automatically, so
var(--wp--preset--color--accent)is usable anywhere, including in your own CSS.
A malformed theme.json is discarded silently and the theme falls back to defaults — validate the JSON whenever styling stops applying.
Template parts
parts/header.html:
<!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"}} -->
<div class="wp-block-group">
<!-- wp:site-title /-->
<!-- wp:navigation {"layout":{"type":"flex"}} /-->
</div>
<!-- /wp:group -->
parts/footer.html follows the same shape. Register them in theme.json if you want friendly labels in the editor:
{
"templateParts": [
{ "name": "header", "title": "Header", "area": "header" },
{ "name": "footer", "title": "Footer", "area": "footer" }
]
}
Templates worth adding next
| File | Used for |
|---|---|
single.html |
Individual posts |
page.html |
Individual pages |
archive.html |
Category, tag and date archives |
search.html |
Search results |
404.html |
Not found |
front-page.html |
The static homepage |
WordPress falls back through the hierarchy, so index.html alone produces a functioning site while you add the rest.
Build visually, then export
The efficient workflow:
- Activate the theme with its minimal files.
- Open Appearance → Editor and design templates and parts visually.
- Adjust global styles in the editor to see how palette and typography feel in place.
- Install the Create Block Theme plugin.
- Use it to save the changes back into theme files, or export a zip.
Without that last step, everything you designed lives in the database as customisations rather than in the theme — fine for one site, useless for distribution or version control.
Add code where theme.json cannot reach
functions.php stays small in a block theme:
<?php
add_action('after_setup_theme', function () {
add_theme_support('wp-block-styles');
add_theme_support('editor-styles');
add_editor_style('editor.css');
});
// Register a pattern directory entry, a block style, or an extra stylesheet here.
For anything site-specific rather than design-specific — custom post types, shortcodes, integrations — use a plugin instead. Functionality in a theme disappears the moment the theme is switched.
Test before you call it done
- Every template: home, single, page, archive, search, 404.
- Widths at mobile, tablet and desktop.
- Both light and dark colour settings if you offer them.
- Editor and front end side by side — they should match.
- With a real post containing long titles, wide images, tables and embeds.
- With no featured image set, which breaks more themes than any other case.
If styles apply in the editor but not on the front end, the usual cause is a missing add_editor_style or a stylesheet that never enqueued — CSS not loading covers how to trace it.
Frequently asked
- A style.css with the theme header, an index.html in a templates folder, and a theme.json. A screenshot and a functions.php are optional but expected in practice.
- Only for things theme.json cannot express — registering block patterns and styles, enqueueing an extra stylesheet, or adding editor features. Many block themes have a very short one.
- The Create Block Theme plugin exports the current templates and global styles into the theme directory, turning database customisations into files you can version control.