Himanshu SharmaSoftware engineer
Let’s talk

WordPress · Lesson 18 of 45

Your first block theme

Build a minimal WordPress block theme from scratch with style.css, theme.json, an index template and header and footer parts, then activate it and see it work.

14 minBlock themesUpdated 23 October 2026

By the end of this lessonCreate the files a block theme needs, understand what each one does, and activate your theme on a real site.

What we will build

In this section you will build a small block theme for a bakery, step by step. A block theme needs surprisingly little:

text
wp-content/themes/bakery/
├── style.css            the theme header (and optional CSS)
├── theme.json           settings and styles (next lesson)
├── templates/
│   └── index.html       the fallback template for every page
└── parts/
    ├── header.html      shared header
    └── footer.html      shared footer

Only style.css and templates/index.html are required. The rest makes the theme pleasant to build on.

style.css: the theme header

WordPress identifies a theme by the comment at the top of style.css:

css
/*
Theme Name: Bakery
Theme URI: https://example.com/bakery
Author: Your Name
Description: A small block theme for a neighbourhood bakery.
Version: 1.0.0
Requires at least: 6.6
Requires PHP: 7.4
License: GNU General Public License v2 or later
Text Domain: bakery
*/

Theme Name is the only required field. Text Domain is used for translations and should match the folder name. Block themes usually keep style.css for the header only and put their design in theme.json.

theme.json

A minimal theme.json sets a content width, so text lines are not too long to read:

json
{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {
		"layout": { "contentSize": "680px", "wideSize": "1100px" }
	}
}

The $schema line gives code editors autocomplete and validation. The next lesson is all about this file.

Template parts

A template part is a piece shared by many templates. The header shows the site title (linked to the home page) and the tagline:

html
<!-- wp:group {"tagName":"header","layout":{"type":"flex","justifyContent":"space-between"}} -->
<header class="wp-block-group">
	<!-- wp:site-title {"level":0} /-->
	<!-- wp:site-tagline /-->
</header>
<!-- /wp:group -->
html
<!-- wp:group {"tagName":"footer"} -->
<footer class="wp-block-group">
	<!-- wp:paragraph -->
	<p>Fresh bread, every morning from 7am.</p>
	<!-- /wp:paragraph -->
</footer>
<!-- /wp:group -->

These files contain exactly the block markup you saw in the block editor lesson. Site Title and Site Tagline are dynamic blocks: they read the current values from Settings → General each time the page is shown. The Site Title has {"level":0}: by default it renders as an h1 heading on every page, which would give each post page two top-level headings (the site title and the post title). Level 0 makes it a plain paragraph, so the page's own title can be the single h1.

The index template

templates/index.html is the fallback template: WordPress uses it for any page that has no more specific template (the template hierarchy lesson explains the order). It pulls in the header, lists posts with a Query Loop, and adds the footer:

html
<!-- 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-excerpt /-->
		<!-- /wp:post-template -->
		<!-- wp:query-no-results -->
			<!-- wp:paragraph --><p>Nothing found.</p><!-- /wp:paragraph -->
		<!-- /wp:query-no-results -->
	</div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->

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

Key parts:

  • wp:template-part {"slug":"header"} loads parts/header.html.
  • wp:query {"query":{"inherit":true}} is a Query Loop that uses the main query: whatever WordPress decided this URL should show (the latest posts on the home page, one post on a single post page, matching posts on a category page).
  • wp:post-template repeats its inner blocks for each post; here a linked title and an excerpt.
  • wp:query-no-results is shown when nothing matches.
  • The constrained layout on main applies the contentSize from theme.json.

Activate it

The theme now appears in Appearance → Themes. Activate it there, or:

bash
wp theme activate bakery
wp theme list --fields=name,status,version
result
Success: Switched to 'Bakery' theme.
+-------------------+----------+---------+
| name              | status   | version |
+-------------------+----------+---------+
| bakery            | active   | 1.0.0   |
| twentytwentyfive  | inactive | 1.5     |
| twentytwentyfour  | inactive | 1.6     |
| twentytwentythree | inactive | 1.7     |
+-------------------+----------+---------+

Let's add a tagline and look at the home page. The header and footer extracts show the template parts' text, and the main extract shows what the Query Loop produced:

bash
wp option update blogdescription "Baked fresh daily"
result
Success: Updated 'blogdescription' option.
http
GET /
result
My WordPress Site
Baked fresh daily
http
GET /
result
Hello world!
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
http
GET /
result
Fresh bread, every morning from 7am.

The home page lists the one post a new site has ("Hello world!") with its excerpt, between our header and footer. Because there is only an index.html template, the single post page, pages and archives all use the same layout for now; the templates lesson adds proper ones.

The theme in the editor

Open Appearance → Editor and you can edit this theme visually. When you save a change to a template or part, WordPress stores a customised copy in the database (as in the Site Editor lesson), leaving your files untouched. While developing, remember to reset customisations or export them back into your files, or you may wonder why your file changes do not show.

Screenshot

Themes usually include a screenshot.png (1200 by 900 pixels) in the theme folder, which Appearance → Themes shows as the preview. Without one, the theme shows a blank card.

Common mistakes

  • Missing templates/index.html, so WordPress treats the folder as a broken theme.
  • Invalid block markup: an unclosed <!-- wp:group --> comment breaks the layout. Build templates in the Site Editor and export them if you are unsure.
  • Forgetting {"inherit":true} on the main Query Loop, so every page shows the same latest posts.
  • Editing files while a customised copy exists in the database and wondering why nothing changes.

Exercise

Add a second paragraph to the footer part with a link to a page called "Contact", and make the header show the tagline only (remove the site title). Check the home page.

Show one possible answer

In parts/footer.html, inside the footer group, add:

html
<!-- wp:paragraph -->
<p><a href="/contact/">Contact us</a></p>
<!-- /wp:paragraph -->

In parts/header.html, delete the line <!-- wp:site-title {"level":0} /-->. Reload the home page: the header shows "Baked fresh daily" and the footer has the new link. If nothing changes, check Appearance → Editor for a customised copy of the part and reset it.

Key points

  • A block theme is a folder in wp-content/themes with at least style.css (the theme header) and templates/index.html.
  • Templates are HTML files made of block markup; template parts such as parts/header.html are shared pieces.
  • theme.json configures settings and styles; it is optional but nearly every block theme has one.
  • WordPress reads the theme header comment in style.css to list the theme in Appearance → Themes.

Check yourself

1. Which two files does WordPress need to recognise a block theme?
2. What is inside a block template file?
3. How does a template include the header part?
4. What does the Post Template block inside a Query Loop do?