Adding Simple Pagination to an 11ty Collection

11ty can handle pagination really well, but finding the right subset of the many pagination features can be a little difficult to figure out. In my case, I just wanted something really simple so that I didn't end up with endlessly scrolling list pages as this site grows. Since this site uses Tailwind, I decided to aim for something similar to the simple card footer from Tailwind UI.

At its simplest, page data can be enabled by adding the pagination key to your template's front matter. The first example from the pagination docs shows how to page over a dataset also defined in your front matter:

---
pagination:
data: testdata
size: 2
testdata:
- item1
- item2
- item3
- item4
---
<ol>
{%- for item in pagination.items %}
<li>{{ item }}</li>
{% endfor -%}
</ol>

In my case, I was looking to add pagination to a collection. My existing template set a variable for the collection, and then used a filter to reverse it.

{% set postslist = collections.all %}

{% for post in postslist | reverse %}
{# iterate through posts #}
{% endfor %}

We can instead use the pagination key in our front matter to prepare this data:

---
title: "All Updates"
pagination:
data: collections.all
size: 5
alias: postslist
reverse: true
---
{% for post in postslist %}
{# iterate through posts #}
{% endfor %}

We specified the collection that should be used as the dataset, the number of posts that should display on each page, and reversed the results. And by using the alias of postslist I didn't have to change variable names in any of my existing markup. You could instead use the default pagination.items if you were starting from scratch.

Now on to the pager itself. First let's look at displaying the current page and result set.

{% if pagination.pageLinks.length > 1 %}
<div class="pb-6 sm:pb-0">
<p class="text-md text-gray-500">
Showing
<span class="font-medium">

{{ (pagination.pageNumber * pagination.size) + 1 }}
</span>
to
<span class="font-medium">

{% if pagination.nextPageLink %}
{{ (pagination.pageNumber * pagination.size) + pagination.size }}
{% else %}
{{ collections.all.length }}
{% endif %}
</span>
of
<span class="font-medium">
{{ collections.all.length }}</span>
results
</p>
</div>

{% endif %}

As you can see in that code snippet, 11ty's gives us a bunch of helpful data in the pagination object. We can use pagination.pageLinks.length to determine if there is even enough data to page - if there is only one page, we don't render the pagination at all. We're also doing a tiny little bit of math to determine the current range of posts that are displayed (I should have warned you that there would be math.) In (pagination.pageNumber * pagination.size) + 1 we're adding 1 because page number is zero indexed. And we can use collections.all.length for the overall number of posts.

Next, let's add the previous and next links. I based this on an example from an 11ty github issue:

<nav class="pagination">
{% if pagination.previousPageLink %}
<a class="pagination__item" href="{{ pagination.previousPageHref }}">Previous</a>
{% else %}
<span class="pagination__item">Previous</span>
{% endif %}
{% if pagination.nextPageLink %}
<a class="pagination__item" href="{{ pagination.nextPageHref}}">Next</a>
{% else %}
<span class="pagination__item">Next</span>
{% endif %}
</nav>

If there is a previousPageLink or nextPageLink we're displaying as a link, otherwise we're displaying as a span to indicate that the option is disabled. And the link to the previous or next page of results is available in pagination.previousPageHref and pagination.nextPageHref respectively.

After layering in some Tailwind classes the end result looked like this:

Pagination example

Putting it all together, here's the final pagination partial. I'm using a variable length rather than collections.all.length so this pagination partial can be used with other collections.

Resources: