Dynamic heading levels in Eleventy

A list of tags for this post.

One of my favorite ways to learn is to look around repositories to see different approaches, or figure out how to do something. I picked up this helpful approach while looking at a repository to learn about something else.

After reading this article by Tomas Pustelto on optimizing CSS I was looking around the repository for his site to take a closer look at how he structured his CSS, and noticed he was dynamically assigning heading levels based on the layout. I made a note to take a closer look after optimizing my CSS, and now that I’ve implemented dynamic headings I wanted to share the approach since I haven’t found any how-to articles already out there.

What are heading levels and why are they important?


Heading levels refer to the HTML heading elements of h1 - h6. Heading levels are important because they describe the structure of the content, and using them in the correct order has several benefits. In addition to helping sighted users find what they’re looking for more quickly, assistive technology users can also easily navigate between or skip headings.

Why am I using dynamic header levels?


There are several places throughout the site where I list posts. I have a single partial to handle all the different post types that includes the post title, summary, the section if applicable (Articles or Notes), publish date and tags. It’s used on the homepage, the Changelog, and listing pages for Articles, Notes, and tags (e.g., the tag for Eleventy content).

An example of an article summary
The article summary that displays on the homepage, listing pages and the changelog. The title, summary and publish date are always displayed and the "Filed Under" section and tags are conditional.

On all of these pages except the homepage the heading should be a h2. On the homepage the listing is within a section that has a h2, so those headings should be h3.

The document outline of the homepage and the articles listing page
The document outline of the homepage and the Articles listing page. On the homepage the Articles listing is within a "Recent Writing" section that has a ```h2```. Outlines via W3C Validator

Fortunately this is relatively easy to do! I’m using Nunjucks if expressions and set variable, but I think you could use the same approach with Liquid by using assign.

How to do it


Here’s a diagram showing the relationship of the partial and layouts…

A diagram showing how the partial and layouts work together.
The partial is passed into two layouts, one for the homepage and one for the listing pages. If the variable is set in the layout then the conditional header uses the variable, if not it renders a ```h2```. Created using Excalidraw.

As the diagram above shows, the partial for the article summary contains a conditional header that sets the value, in this case the heading tag, to a variable titled headingLevel. If headingLevel is not present, then h2 is used. This makes the default heading a h2 but allows for variation where needed.

<!-- article-summary.html -->
<{{ headingLevel if headingLevel else 'h2' }} class="promo-article-title">
<a href="{{ item.url }}">{{ item.data.title }}</a>
</{{ headingLevel if headingLevel else 'h2' }}>

On the Articles, Changelog, Notes and tag listing pages the conditional heading code turns into this…

<h2 class="promo-article-title">
<a href="{{ item.url }}">{{ item.data.title }}</a>

In the homepage layout headingLevel is set to h3

<!-- home.html -->
{% extends "layouts/base.html" %}

{% set headingLevel = 'h3' %}

{% block content %}{% endblock%}

Which turns the conditional heading code in the partial to this…

<h3 class="promo-article-title">
<a href="{{ item.url }}">{{ item.data.title }}</a>

That’s it! A straightforward way to efficiently use partials and ensure you’re creating proper document outlines.

Useful resources

A list of tags for this post.

Enjoying this site? Leave me a tip at Ko-fi!

Back to top