Creating a Block Theme
By the end of this lesson you will have built your own block theme from empty files. It will have a header, a footer, a blog list, single post and page layouts, and a ready-made hero pattern. You will understand every file, so you can build themes for clients with confidence.
Building a theme is like setting up a new shop. First the signboard (style.css), then the house rules (theme.json), then the room layouts (templates), the shared entrance and exit (header and footer parts), and finally some ready-made display stands (patterns).
What a block theme needs
Only two files are strictly required:
style.csswith the theme headertemplates/index.html, the fallback template for every page
Everything else is optional but useful. Here is the complete theme we will build:
bright-block/
style.css
theme.json
templates/
index.html
single.html
page.html
404.html
parts/
header.html
footer.html
patterns/
hero.phpStep 1: style.css
Create wp-content/themes/bright-block/style.css:
/*
Theme Name: Bright Block
Author: Your Name
Description: A small block theme for a coaching centre.
Version: 1.0.0
Requires at least: 6.6
Requires PHP: 8.0
License: GNU General Public License v2 or later
Text Domain: bright-block
*/Go to Appearance > Themes. "Bright Block" already appears, though it will show a message about a missing template until Step 3.
Step 2: theme.json
Add the settings from the previous lesson, plus a list of template parts so the Site Editor knows what they are:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"appearanceTools": true,
"color": {
"palette": [
{ "slug": "primary", "name": "Primary", "color": "#21759b" },
{ "slug": "accent", "name": "Accent", "color": "#f28c28" },
{ "slug": "base", "name": "Base", "color": "#ffffff" },
{ "slug": "contrast", "name": "Contrast", "color": "#1d2327" }
]
},
"layout": { "contentSize": "720px", "wideSize": "1200px" }
},
"styles": {
"typography": { "fontFamily": "system-ui, sans-serif", "lineHeight": "1.6" },
"elements": { "link": { "color": { "text": "var(--wp--preset--color--primary)" } } }
},
"templateParts": [
{ "name": "header", "title": "Header", "area": "header" },
{ "name": "footer", "title": "Footer", "area": "footer" }
]
}appearanceTools: true switches on a bundle of design controls in the editor at once, such as borders, padding, margins and link colours.
Step 3: The header and footer parts
Template parts live in parts/. Create parts/header.html:
<!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"},"style":{"spacing":{"padding":{"top":"1rem","bottom":"1rem"}}}} -->
<div class="wp-block-group" style="padding-top:1rem;padding-bottom:1rem">
<!-- wp:site-title /-->
<!-- wp:navigation {"overlayMenu":"mobile"} /-->
</div>
<!-- /wp:group -->And parts/footer.html:
<!-- wp:group {"backgroundColor":"contrast","textColor":"base","layout":{"type":"constrained"}} -->
<div class="wp-block-group has-base-color has-contrast-background-color has-text-color has-background">
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Bright Future Coaching, Rohtak. Classes 9 to 12.</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->Notice the pattern: the HTML comment describes the block and its settings in JSON, and the HTML inside is what the block saves. For blocks like Site Title and Navigation, the comment ends with /-->, because their HTML is created fresh on every page load. These are called dynamic blocks.
Writing block markup by hand is fiddly. A common trick is to build the design in the editor, switch to the Code editor, copy the markup and paste it into your file.
Step 4: The index template with a Query Loop
Create templates/index.html. It shows a list of posts. The Query Loop block fetches posts, and Post Template repeats its inner blocks for each one:
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:query {"query":{"inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-date /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
<!-- wp:query-no-results -->
<!-- wp:paragraph --><p>No posts yet. Check back soon.</p><!-- /wp:paragraph -->
<!-- /wp:query-no-results -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->"inherit": true is important. It means "show whatever posts this URL asks for". So on the blog page it lists the newest posts, on a category page it lists that category, and on a search page it lists the results. The same template works everywhere.
Now activate Bright Block and visit your site. You should see the site title and menu, a list of posts with dates and excerpts, and the dark footer.
Step 5: Single and page templates
Create templates/single.html for individual 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-date /-->
<!-- wp:post-featured-image /-->
<!-- wp:post-content {"layout":{"type":"constrained"}} /-->
<!-- wp:post-terms {"term":"category"} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->Copy it to templates/page.html and remove the date and category lines, because pages have no date or categories. Add a simple templates/404.html with a heading "Page not found" and a Search block (<!-- wp:search {"label":"Search"} /-->).
Open a post on your site. It should use the single layout with the date and categories.
Step 6: A block pattern
Files in patterns/ are registered automatically. Each one is a PHP file with a header comment. Create patterns/hero.php:
<?php
/**
* Title: Hero with call to action
* Slug: bright-block/hero
* Categories: banner, call-to-action
* Description: A large heading, a line of text and a demo-class button.
*/
?>
<!-- wp:group {"align":"full","backgroundColor":"primary","textColor":"base","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull has-base-color has-primary-background-color has-text-color has-background">
<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading"><?php echo esc_html__( 'Small batches. Big results.', 'bright-block' ); ?></h1>
<!-- /wp:heading -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button {"backgroundColor":"accent"} -->
<div class="wp-block-button"><a class="wp-block-button__link has-accent-background-color has-background wp-element-button" href="/contact/"><?php echo esc_html__( 'Book a free demo class', 'bright-block' ); ?></a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->Because the file is PHP, you can make the text translatable. Open the block inserter, click Patterns and look under Banners: "Hero with call to action" is there. You can also place it straight into a template with <!-- wp:pattern {"slug":"bright-block/hero"} /-->.
Step 7: Optional functions.php
Block themes do not need functions.php, but you can add one for extra PHP, such as registering a block style or a custom image size. Keep it small; features that should survive a theme change belong in a plugin.
Common mistakes
- No
templates/index.html. WordPress reports the theme as broken. - Broken block markup. A missing closing comment such as
<!-- /wp:group -->makes the editor show "This block contains unexpected or invalid content". Copy markup from the Code editor to avoid this. - Forgetting
inherit: trueon the main Query Loop, so category and search pages all show the same latest posts. - Editing in the Site Editor and expecting the files to change. Site Editor saves go to the database unless you use Create Block Theme to save them to files.
Key points
- A block theme needs only
style.cssandtemplates/index.html;theme.json, parts and patterns make it useful. - Templates and parts are HTML files of block markup; dynamic blocks use self-closing
/-->comments. - The Query Loop with
"inherit": trueshows whatever posts the current URL asks for. - Template parts in
parts/are pulled into templates with thetemplate-partblock. - PHP files in
patterns/with a header comment become patterns automatically. - The Create Block Theme plugin can save Site Editor work back into theme files.
Exercise
Add an archive.html template to Bright Block that shows the archive title (use the query-title block with {"type":"archive"}), then a Query Loop that inherits the query and shows each post's title, featured image and excerpt in a grid of three columns. Check it by opening a category page.
Show solution
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:query-title {"type":"archive"} /-->
<!-- wp:query {"query":{"inherit":true},"align":"wide"} -->
<div class="wp-block-query alignwide">
<!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
<!-- wp:post-featured-image {"isLink":true} /-->
<!-- wp:post-title {"isLink":true,"level":3} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->Category, tag and date pages now use this template instead of index.html, because archive.html is more specific. The next lesson explains why.
Quick Quiz
1. Which two files does a block theme strictly need?
These two are the minimum; everything else is optional.
2. What does "inherit": true on a Query Loop do?
The loop follows the main query, so one template works for many archives.
3. Where do template parts live in a block theme?
Template parts such as header and footer are HTML files in parts/.
4. How do you register a pattern in a block theme without any code in functions.php?
Files in patterns/ with a Title and Slug header are registered automatically.
Learn WordPress with a mentor
Free lessons get you started. Our WordPress & Website Building training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
