Templates and template parts

Level: ,

In this exercise, you will learn to create templates and template parts for block themes.

Templates are your base files. You will start with index.html and create additional templates.

You will use template parts to organize and add structure to the theme to avoid having to repeat code. You will start with a site header and footer and combine them with the template files.

For this full site editing lesson, you will need:

  • A WordPress installation with some test content
  • The Gutenberg plugin, version 13.0 or newer
  • A blank theme where you will add your block template files

If you created a blank theme in the previous lesson, continue using it for this exercise.
If you have not already set up a theme, you can download part one of the lesson files from GitHub.

Estimated reading time: 17 minutes

Updated June 25, 2022, for Gutenberg 13.5.

Creating your first template parts

If your starter theme does not already include a folder called templates with an empty index.html file inside, please create them before continuing.

A template part is an HTML file, a custom post type (wp_template_part), and a block.
Sounds complicated? Think of them as blocks that display the content you have placed inside them. Template parts are not required, but they help theme authors structure the theme with reusable, smaller parts.

Inside the themes root folder, create a folder called parts. Now please create the footer.html and header.html files inside this folder.
WordPress will search this folder for valid files, so you will not be able to use a different folder name.

Next, add some content to the template parts so that you can identify them when you test the theme. You can add the site title and tagline blocks to header.html with the following block markup:

<!-- wp:site-title {"textAlign":"center"} /-->
<!-- wp:site-tagline {"textAlign":"center"} /-->

In footer.html, add the well-known footer credit text:

<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Proudly powered by 
<a href="https://wordpress.org/">WordPress</a>.</p>
<!-- /wp:paragraph -->

Combining templates and template parts

To include a template part inside a template file, you use the block markup for template parts.
Template parts are self-closing because the content is inside the HTML files.

Open index.html and add the two template parts:

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

/parts/header.html is the equivalent of header.php in classic themes.

You can also think of <!-- wp:template-part {"slug":"header"} /--> as the equivalent of get_header(); or a similar include, with one important exception: The block markup adds a wrapping HTML element.

Just like blocks, templates and template parts are self-containing.
The opening tag and the closing tag must be in the same template.
You would not be able to place an opening tag for a group block in a header template and close it in a footer template.

You need to use the correct HTML element for these two important landmarks. Add the tagName attribute for <header> and <footer>:

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

If you want to be able to identify and target the header and footer with CSS, add a custom CSS className:

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

The className attribute is the equivalent of adding the class in the “Additional CSS class(es)” field in the advanced panel in the block settings sidebar:

The additional CSS classes field is under the Advanced section of the block settings sidebar when a block is selected.

If you preview your website now, it should look something like this:

The front of the website should display the template including the header template part with the site title and tagline, and the footer template part with the credit text.

Adding the blog section

You will create the blog using query loop and post template blocks. But first, you need to add a <main> element as a wrapper for the list of blog posts.

In index.html, between the two template parts, add a group block with the tagName attribute with the value main. Next, update the <div> to <main>:

<!-- wp:group {"tagName":"main"} -->
<main class="wp-block-group">
</main>
<!-- /wp:group -->

You also enable the automatic skip link by including the <main> element.

Are you wondering why there is no inner container inside this group? WordPress removed the inner container for full site editing themes in Gutenberg 10.3.

Next, enable the layout setting that you added to theme.json in the previous lesson. Adding the code "layout":{"inherit":true} is the equivalent of enabling the “Inherit default layout” setting in the editor:

The layout panel in the block settings sidebar has a toggle option with the label "Inherit default layout"

When the default layout is inherited, the blocks inside the container block are centered horizontally. The widths are the ones you defined for the contentSize in theme.json.
Without this setting, the blocks are full width and aligned to the left.

<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
</main>
<!-- /wp:group -->

Query loop block

The query loop block has many advanced features. You can rely on the defaults for a basic blog: Display posts ordered by date, descending. A query loop block without any settings inherits the query from the page template. In other words, it displays different content depending on the context.

  • On the index or home template, the query block displays posts from every category
  • In an archive, it shows items from the chosen post type or taxonomy (category or tag)
  • On a search results page, it displays the search results

When you manually add a query in the block editor, this is the equivalent of enabling the “Inherit query from template” setting:

The query loop block has a Settings panel with a toggle option with the label "Inherit query from template".

The post template

Each query block uses an inner block called post template, which is a container block for post blocks like the post title and content.
WordPress passes data from the query to the post template and then repeats the blocks inside the post template for every post.

<!-- wp:query -->
<div class="wp-block-query">
<!-- wp:post-template -->

<!-- /wp:post-template -->
</div>
<!-- /wp:query -->

I want the blog to display a featured image, post title, author, and date above the post excerpt. You can experiment with other post blocks as long as you place them inside the post template.

The post title needs to be a link. Otherwise, visitors can not reach the individual posts from the blog. Including "isLink":true will add the correct link for each post inside the loop.
In the code example, I decided not to display the post author avatar by setting showAvatar to false:

<!-- wp:post-template -->

<!-- wp:post-featured-image /-->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-author {"showAvatar":false} /-->
<!-- wp:post-date /-->
<!-- wp:post-excerpt /-->

<!-- /wp:post-template -->

Not sure what new post blocks you can use? Check out the full site editing block reference page.

Finally, I think I could improve this blog by showing the categories, and the block markup for displaying categories is:

<!-- wp:post-terms {"term":"category"} /-->

You can use the post terms block to display any taxonomy terms, and the category is only one variation.

<!-- wp:post-featured-image /-->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-author {"showAvatar":false} /-->
<!-- wp:post-date /-->
<!-- wp:post-terms {"term":"category"} /-->
<!-- wp:post-excerpt /-->

Pagination

You don’t want the blog to repeat the pagination for each post, so instead, place the pagination block inside the query but outside the post template:

<!-- wp:query -->
<div class="wp-block-query">
<!-- wp:post-template -->
...
<!-- /wp:post-template -->

<!-- wp:query-pagination -->
<div class="wp-block-query-pagination">
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-next /-->
</div>
<!-- /wp:query-pagination -->

</div>
<!-- /wp:query -->

Your index.html file should now look like this:

<!-- wp:template-part {"slug":"header","tagName":"header","className":"site-header"} /-->
<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
	<!-- wp:query -->
        <div class="wp-block-query">
        <!-- wp:post-template -->
	<!-- wp:post-featured-image /-->
	<!-- wp:post-title {"isLink":true} /-->
	<!-- wp:post-author {"showAvatar":false} /-->
	<!-- wp:post-date /-->
	<!-- wp:post-terms {"term":"category"} /-->
	<!-- wp:post-excerpt /-->
       <!-- /wp:post-template -->
	<!-- wp:query-pagination -->
	<div class="wp-block-query-pagination">
		<!-- wp:query-pagination-previous /-->
		<!-- wp:query-pagination-next /-->
	</div>
	<!-- /wp:query-pagination -->
        </div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer","className":"site-footer"} /-->

The front of your website should look something like this:

The index block template shows the header and footer template parts and a single post.

Creating a single post template

I want to show slightly different things for pages and single posts. And to do this; each format needs its own block template, starting with the single post.

Save a copy of index.html as single.html inside the templates folder.
Of course, you only want to show the current post, so you need to remove the loop.
What you have left in single.html is the following code:

<!-- wp:template-part {"slug":"header","tagName":"header","className":"site-header"} /-->
<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
	<!-- wp:post-featured-image /-->
	<!-- wp:post-title /-->
	<!-- wp:post-author {"showAvatar":false} /-->
	<!-- wp:post-date /-->
	<!-- wp:post-terms {"term":"category"} /-->
	<!-- wp:post-excerpt /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer","className":"site-footer"} /-->

Since this is a single post, I also removed the isLink attribute from the post title.

To show the full content, replace <!-- wp:post-excerpt /--> with
<!-- wp:post-content /-->:

<!-- wp:post-featured-image /-->
<!-- wp:post-title /-->
<!-- wp:post-author {"showAvatar":false} /-->
<!-- wp:post-date /-->
<!-- wp:post-terms {"term":"category"} /-->
<!-- wp:post-content /-->

<!-- wp:post-content /--> Is the equivalent of the_content() in a traditional PHP-based theme.

You enable wide and full-width blocks in the post content by adding "align":"full" and "layout":{"inherit":true} to the post content block:

<!-- wp:post-content {"align":"full","layout":{"inherit":true}} /-->

You can show tags below the content using the post tags variation of the post terms block:
<!-- wp:post-terms {"term":"post_tag"} /-->

If you want to add some extra spacing, you can achieve this by adding spacer blocks between the content and the post meta information:

<!-- wp:post-featured-image /-->
<!-- wp:post-title /-->
<!-- wp:post-author {"showAvatar":false} /-->
<!-- wp:post-date /-->
<!-- wp:post-terms {"term":"category"} /-->
<!-- wp:spacer {"height":40} -->
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
<!-- wp:post-content {"align":"full","layout":{"inherit":true}} /-->
<!-- wp:spacer {"height":40} -->
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
<!-- wp:post-terms {"term":"post_tag"} /-->
<!-- wp:spacer {"height":40} -->
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

Next and previous post navigation

The post navigation link block adds a link to the next post, and the link to the previous post is a block variation.
If there is no next or previous post to link to, then the block is not visible on the front of the website. The markup for the two block variations is:

<!-- wp:post-navigation-link {"type":"previous"} /-->
<!-- wp:post-navigation-link /-->

Adding a comments area

Gutenberg version 13.0 deprecates the post comments block in favor of the new comments query loop block. With the new block, you have much greater control of the layout.
You can position, add and remove inner blocks like the comment author’s name, the comment date, and the reply link.

But it also means that there are backward compatibility concerns on WordPress websites where you are not using Gutenberg, so I will show you two ways to display comments.

Showing comments without Gutenberg

If your theme supports WordPress 5.9 and does not require Gutenberg, you need to display comments using a single block called post comments:

<!-- wp:post-comments /-->

Showing comments with the Gutenberg plugin active or with WordPress 6.0

The comments query loop block is set up the same way as the query loop that you used for displaying your blog posts.

It fetches the comments for the current post or page and uses a comment template block as a container for the inner blocks.
The inner blocks are: Avatar, comment author name, comment date, comment edit link, comment content, and comment reply link.
In addition, there is a comments pagination block and a post comments form block.

I suggest creating a comments area below the content, inside the <main> element, by adding a heading and then using the default comments query loop.

<!-- wp:spacer {"height":100} -->
<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
<!-- wp:heading {"className:"comments-title"} -->
<h2 class="comments-title">Comments</h2>
<!-- /wp:heading -->

The markup for the comment blocks is slightly more advanced than what I have covered so far because it includes several nested blocks.
— If you are not used to reading block markup, it is easy to feel overwhelmed and get lost when you try to locate where a block starts and ends.
I recommend that you start by adding the comments query loop block in the WordPress block editor and then copy-paste the code into the template in your code editor.

Let me walk you through the default inner blocks:
First, you have the comments query loop, which is the parent block, and the comment template:

<!-- wp:comments-query-loop -->
<div class="wp-block-comments-query-loop">
<!-- wp:comment-template -->

<!-- /wp:comment-template -->
</div>
<!-- /wp:comments-query-loop -->

The comment template uses a columns block to display the comment author’s avatar next to the comment, horizontally. The leftmost column has a width attribute set to 40px to match the width of the avatar:

<!-- wp:comment-template -->
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"40px"} -->
<div class="wp-block-column" style="flex-basis:40px">
<!-- wp:avatar {"size":40,"style":{"border":{"radius":"20px"}}} /-->
</div><!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">

</div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- /wp:comment-template -->

The second column does not have a width set, and it will use up the remaining space:

<!-- wp:comment-template -->
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"40px"} -->
<div class="wp-block-column" style="flex-basis:40px">
<!-- wp:avatar {"size":40,"style":{"border":{"radius":"20px"}}} /--></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:comment-author-name /-->
<!-- wp:comment-content /-->
<!-- wp:comment-reply-link /-->
</div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- /wp:comment-template -->

The comment date and edit link are placed inside a group block with a row variation, placing them side by side:

<!-- wp:comment-template -->
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"40px"} -->
<div class="wp-block-column" style="flex-basis:40px">
<!-- wp:avatar {"size":40,"style":{"border":{"radius":"20px"}}} /--></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:comment-author-name /-->

<!-- wp:group {"style":{"spacing":{"margin":{"top":"0px","bottom":"0px"}}},"layout":{"type":"flex"}} -->
<div class="wp-block-group" style="margin-top:0px;margin-bottom:0px">
<!-- wp:comment-date /-->
<!-- wp:comment-edit-link /-->
</div>
<!-- /wp:group -->

<!-- wp:comment-content /-->
<!-- wp:comment-reply-link /-->
</div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- /wp:comment-template -->

You can identify a row by the flex layout setting "layout":{"type":"flex"} and the absence of orientation: <!-- wp:group {"layout":{"type":"flex"}} -->

A group block with both a flex layout and a vertical orientation is called a stack:
<!-- wp:group {"layout":{"type":"flex","orientation":"vertical"}} -->

Stack is available from Gutenberg version 13.0.

Comments pagination

Like the post pagination, the comments pagination block is inside the query but outside the comment template. — I placed it below the comments, but if you prefer, you can place it above the comments or why not or in both positions?

<!-- wp:comments-query-loop -->
<div class="wp-block-comments-query-loop">
<!-- wp:comment-template -->
<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column {"width":"40px"} -->
<div class="wp-block-column" style="flex-basis:40px">
<!-- wp:avatar {"size":40,"style":{"border":{"radius":"20px"}}} /--></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:comment-author-name /-->

<!-- wp:group {"style":{"spacing":{"margin":{"top":"0px","bottom":"0px"}}},"layout":{"type":"flex"}} -->
<div class="wp-block-group" style="margin-top:0px;margin-bottom:0px">
<!-- wp:comment-date /-->
<!-- wp:comment-edit-link /-->
</div>
<!-- /wp:group -->

<!-- wp:comment-content /-->
<!-- wp:comment-reply-link /-->
</div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- /wp:comment-template -->

<!-- wp:comments-pagination -->
<!-- wp:comments-pagination-previous /-->

<!-- wp:comments-pagination-numbers /-->

<!-- wp:comments-pagination-next /-->
<!-- /wp:comments-pagination -->
</div>
<!-- /wp:comments-query-loop -->
Comment form

Our comments area is still missing its comments form. The current comments form block is a single block without options to edit the form fields.

The comments form is only visible on the front of your website if comments are open. The block markup for adding the comments form is:

<!-- wp:post-comments-form /-->

And here is a view of the completed single post:

A single post with title and post content, a navigation link for the next post, and a comment area with a single comment, followed by a comment form.

Creating the single page template

I recommend that you create the block template for single pages by saving a copy of single.html as page.html inside the templates folder.
Next, remove the unsupported blocks: post navigation, category, and tags.

— I also decided to remove the date, author, and comments, But this is a personal choice, and you can choose which blocks to include.

<!-- wp:template-part {"slug":"header","tagName":"header","className":"site-header"} /-->
<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
	<!-- wp:post-featured-image /-->
	<!-- wp:post-title /-->
	<!-- wp:spacer {"height":40} -->
	<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
	<!-- /wp:spacer -->
	<!-- wp:post-content {"align":"full","layout":{"inherit":true}} /-->
	<!-- wp:spacer {"height":100} -->
	<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
	<!-- /wp:spacer -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer","className":"site-footer"} /-->

Archive templates

You will create a basic archive page in this part of the exercise. You can create an archive.html file used for all taxonomies or combine it with different templates for categories (category.html) or tags (tag.html). If you want to create a template for a specific category, use the file name category-{slug of the category}.html.

If you don’t include the archive block templates in your theme, WordPress will fall back to using index.html, and your website will still show the correct content. The reason why I am suggesting that you create separate templates is to allow for customization and to display an archive title.

Open the index.html file in your code editor and save a copy of the file as archive.html.
In your archive.html file, add an archive title, a variation of the query title block, above the query:

<!-- wp:query-title {"type":"archive"} /-->

Categories, tags, and other taxonomies can optionally have a description. To display the description, you can use the term description block:

<!-- wp:term-description /-->

The full code of the archive.html file is now:

<!-- wp:template-part {"slug":"header","tagName":"header","className":"site-header"} /-->
<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
        <!-- wp:query-title {"type":"archive"} /-->
	<!-- wp:term-description /-->
	<!-- wp:query -->
        <div class="wp-block-query">
	<!-- wp:post-template -->
	<!-- wp:post-featured-image /-->
	<!-- wp:post-title {"isLink":true} /-->
	<!-- wp:post-author {"showAvatar":false} /-->
	<!-- wp:post-date /-->
	<!-- wp:post-terms {"term":"category"} /-->
	<!-- wp:post-excerpt /-->
	<!-- /wp:post-template -->
	<!-- wp:query-pagination -->
	<div class="wp-block-query-pagination">
		<!-- wp:query-pagination-previous /-->
		<!-- wp:query-pagination-next /-->
	</div>
	<!-- /wp:query-pagination -->
        </div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer","className":"site-footer" /-->

Customizing the search results page

With the addition of the ”no results block” in Gutenberg version 12.9, you can finally customize the search results page to display a message instead of a blank page when there are no search results. The no results block is in an early, simplified version, and I hope to continue working on improving it. Like the post and comment template, it is used inside a query and is a container block where you place other blocks.

Copy index.html and save it as search.html inside the templates folder.

Optionally, add a heading block above the query to show users that they are viewing the search page:

<!-- wp:heading -->
<h2>Search</h2>
<!-- /wp:heading -->

WordPress shows the blocks inside the no results block instead of the post template, so it matters less where you position it as long as it is inside the query:


<!-- wp:template-part {"slug":"header","tagName":"header","className":"site-header"} /-->
<!-- wp:group {"tagName":"main","layout":{"inherit":true}} -->
<main class="wp-block-group">
	<!-- wp:heading -->
	<h2>Search</h2>
	<!-- /wp:heading -->
	<!-- wp:query -->
	<div class="wp-block-query">
	<!-- wp:post-template -->
		<!-- wp:post-featured-image /-->
		<!-- wp:post-title {"isLink":true} /-->
		<!-- wp:post-author {"showAvatar":false} /-->
		<!-- wp:post-date /-->
		<!-- wp:post-terms {"term":"category"} /-->
		<!-- wp:post-excerpt /-->
	<!-- /wp:post-template -->
	<!-- wp:query-pagination -->
	<div class="wp-block-query-pagination">
		<!-- wp:query-pagination-previous /-->
		<!-- wp:query-pagination-next /-->
	</div>
	<!-- /wp:query-pagination -->

	<!-- wp:query-no-results -->

	<!-- /wp:query-no-results -->
	</div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer","className":"site-footer"} /-->

You can add a basic message, a search form, a list of the latest posts, or even use a second query:

<!-- wp:query-no-results -->
<!-- wp:heading {"textAlign":"left","level":3} -->
<h3 class="has-text-align-left">No results found</h3>
<!-- /wp:heading -->

<!-- wp:paragraph {"align":"left"} -->
<p class="has-text-align-left">Sorry, but nothing matched your search terms. Please try again with some different keywords.</p>
<!-- /wp:paragraph -->

<!-- wp:spacer {"height":"50px"} -->
<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:search {"label":"Search","showLabel":false,"buttonText":"Search"} /-->

<!-- wp:spacer {"height":"50px"} -->
<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:heading {"level":3} -->
<h3>Looking for a specific topic?</h3>
<!-- /wp:heading -->

<!-- wp:categories /-->
<!-- /wp:query-no-results -->

Front view of the search results page:

The search template displays the site title, a heading with the text "No results found", a search form block, and a list of the categories on the site.

Next steps

You have probably already thought of some ways to improve these basic templates.
For practice, move the comments area to a template part and include it in the appropriate template.

From here I recommend skipping to the next chapter where you will learn how to add more options to theme.json. You can also choose to learn more about different templates first.