Series: 11ty Theme
tl;dr: How I created lists of posts that are filtered to certain tags or by year of the post.
Series Description: However many years later, I have actually done something resembling completing the design of this site using eleventy aka 11ty! I'm sure there are still more things I'll decide to change later, but the general idea I'm pretty happy with. This series describes some of the main challenges I faced. You can navigate other posts in the series using the list of links in the sidebar.
The most essential challenge in building these eleventy sites was handling generating post lists based on some filtering variable, particularly by tag and by year. This allowed me to build pages that are only showing all the posts tagged as Accessibility, for example, or all the posts published in 2026.
The Post List Template
The post-list.njk template in 11ty-theme handles the looping and generating of each post:
{# Loop for a list of posts, passed in as postList. #}
{%- css %}.post-list { counter-reset: start-from {{ (postListCounter or postList.length) + 1 }} }{% endcss %}
{%- css %}{% include "11ty-theme/css/post-list.css" %}{% endcss %}
{# Posts #}
{% for post in postList %}
<article class="border-bottom-section">
<h2 class="post-list-item-title"><a href="{{ post.url }}" class="post-list-item-link">{% if post.data.title %}{{ post.data.title }}{% else %}<code>{{ post.url }}</code>{% endif %}</a></h2>
{% if post.data.series %}
<div class="post-list-metadata"><p>Series: {{ post.data.series }}</p></div>
{% endif %}
<div class="post-list-metadata">
<p class="post-list-item-time-container"><time class="post-list-item-time" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate("LLLL d, yyyy") }}</time></p>
{% if post.data.tags | filterTagList | length %}
<p>Tags:
{%- for tag in post.data.tags | filterTagList %}
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
<a href="{{ tagUrl }}" class="post-list-tag"{% if tagDescriptions[tag] %} title="{{ tagDescriptions[tag] }}"{% endif %}>{{ tag }}</a>{%- if not loop.last %}, {% endif %}
{%- endfor %}
</p>
{% endif %}
</div>
{% if post.data.description %}<p class="post-list-item-description">{{ post.data.description }}</p>{% endif %}
</article>
{% endfor %}
That involves pagination in some ways that were the most complicated component of this site to get to work with eleventy. I'll touch on that more in a future post.
Tag Filter
To be able to get all posts but with a certain tag or by a certain year, we'll need some filters and collections added to the theme's config.js file:
The filter filterTagList is defined in the config.js of the theme, grabbing all tags which have at least one published post and then sorting them alphabetically:
eleventyConfig.addFilter("filterTagList", function filterTagList(tags) {
const publishedPosts = this.ctx.collections.posts;
const publishedTags = new Set(publishedPosts.flatMap(post => post.data.tags).filter(Boolean));
return (tags || [])
.filter(tag => publishedTags.has(tag))
.filter(
(tag) => ["all", "posts", "sidebar", "postsByTag", "postsByYear", "postsByYear.pages", "postsByTag.pages"].indexOf(tag) === -1,
)
.sort();
});
Generating Tag Pages
Tag pages, from each individual site under content/tag-page.njk, can now be generated.
---
pagination:
data: collections.postsByTag.pages
size: 1
alias: tagPage
permalink: /tags/{{ tagPage.key | slugify }}/{% if tagPage.pageNumber > 0 %}page{{ tagPage.pageNumber + 1 }}/{% endif %}
eleventyComputed:
title: 'Tagged "{{ tagPage.key }}"{% if tagPage.pageNumber > 0 %} (Page {{ tagPage.pageNumber + 1 }}){% endif %}'
---
{% if tagDescriptions[tagPage.key] %}
<p class="tag-description">{{ tagDescriptions[tagPage.key] }}</p>
{% endif %}
{% set postList = tagPage.posts %}
{% set pagination = tagPage %}
{% include "post-list-pager.njk" %}
This gives a page for each tag, and each of those pages have pagination (to be explained in the future post), with distinct permalinks that can be browsed directly.
Year Pages
The pages by year are similar to tag pages, but in content/year-page.njk:
---
pagination:
data: collections.postsByYear.pages
size: 1
alias: yearPage
permalink: /{{ yearPage.key }}/{% if yearPage.pageNumber > 0 %}page{{ yearPage.pageNumber + 1 }}/{% endif %}
eleventyComputed:
title: 'Posts from {{ yearPage.key }}{% if yearPage.pageNumber > 0 %} (Page {{ yearPage.pageNumber + 1 }}){% endif %}'
---
<p>Looking for all posts published in {{ yearPage.key }}? You've come to the right place.</p>
{% set postList = yearPage.posts %}
{% set pagination = yearPage %}
{% include "post-list-pager.njk" %}
This one is defined as a collection, not a filter, also in the theme's config.js:
eleventyConfig.addCollection("postsByYear", collectionApi => {
return createPagedCollection(collectionApi, {
grouperFn: (post) => post.date.getFullYear(),
pageSize: 10,
keySort: 'desc',
permalink: (key, pageNumber) => {
if (pageNumber === 1) {
return `/${key}/`;
}
return `/${key}/page${pageNumber}/`;
}
});
});
Previous: Email: Proton Part 2
Next: Sidebars