Building a Responsive Page Navigation for Large Sites

Lurking around the Jekyll/Liquid-verse I’ve come upon a few snippets of code to navigate between pages on an index page, blog-style, displaying a number of pages using the paginate-function in Jekyll. Unfortunately, I have a large number of posts, which results in a large number of pages… more than what will fit on one row of nicely designed page links.

So, I’ve decided to try to concoct something on my own…

To follow along, you need to install pagination, see the jekyll-paginate page also check out the pagination documentation.

You define the number of pages in your _config.yml as paginate: N where N is the number of posts per page.

Pagination behavior

The first step, when it becomes a bit complex like a pagination script can be, is to specify what we want it to do. For this one, I basically decided to mimic the behavior of Google Search.

Mobile and Tablet

For mobile and tablet Google only ever displays three links:

  • First page
  • Previous page
  • Next page

The rules for when displaying these links are as follows:

  1. On page 1
    • Display Next
  2. On page 2
    • Display Prev and Next.
  3. On page 3 and higher
    • Display First, Prev and Next.
  4. Only show Next while we’re not on the last page.
  5. Never show pagination if there’s only one page

Not so complicated!

Desktop

The desktop pagination has a total of 12 links. Prev, Next and ten direct links to pages.

The rules for when displaying these links are as follows:

  1. On page 1:
    • Display direct page link 1-10 and Next
  2. On page 2-6:
    • Display Prev, direct page link 1-10, and Next
  3. On page 7 and above:
    • Display Prev and Next as usual, then if the current page is p, display direct page links p-5-p+4
    • However, when we get closer to the last page we will, for instance on the second to last page display p-8-p+1
  4. Only show Next while we’re not on the last page.
  5. Special rules apply if we have less than 10 pages of posts
  6. Never show pagination if there’s only one page

Not quite as simple as for the mobile/tablet, but have a look at the code (that turned out much simpler… hmmm…)

Combining Mobile, Tablet and, Desktop

Jekyll don’t do any logic once the pages has been generated, so any differences between desktop and mobile/tablet has to be controlled with CSS and the standard @media-queries used to design responsive pages in general.

To do this, we need different CSS-classes for each platform, and then we have to assign classes to elements in the design according to the following:

  • Visible on mobile and tablet only:
    • First
  • Visible on desktop only:
    • Direct page links
  • Visible on all platforms:
    • Prev and Next

Code

Let’s get coding!

HTML and Liquid

At an appropriate location in your index.html add the following:

{% if paginator.total_pages > 1 %}
<div class="pagination">
  <!-- first page link -->
  {% if paginator.page > 2 %}
    <a href="{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}" class="first">&laquo; First</a>
  {% endif %}
  
  <!-- previous page link -->
  {% if paginator.previous_page %}
    <a href="{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}">&laquo; Prev</a>
  {% endif %}
  
  <!-- direct page links -->
  {% if paginator.page > 6 %}
    {% assign first_page = paginator.page | minus: 5 %}
  {% else %}
    {% assign first_page = 1 %}
  {% endif %}
  
  {% assign last_page = first_page | plus: 9 %}
  {% if last_page > paginator.total_pages %}
    {% assign last_page = paginator.total_pages %}
    {% assign first_page = last_page | minus: 9 %}
    {% if 1 > last_page %}
      {% assign last_page = 1 %}
    {% endif %}
  {% endif %}

  {% for page in (first_page..last_page) %}
    {% if page == paginator.page %}
      <span class="direct-link">{{ page }}</span>
    {% elsif page == 1 %}
      <a href="{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}" class="direct-link">{{ page }}</a>
    {% else %}
      <a href="{{ site.paginate_path | prepend: site.baseurl | replace: '//', '/' | replace: ':num', page }}" class="direct-link">{{ page }}</a>
    {% endif %}
  {% endfor %}

   <!-- next page link -->
  {% if paginator.next_page %}
    <a href="{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}">Next &raquo;</a>
  {% endif %}
</div>
{% endif %}

Let’s go through the code a step at a time.

If there’s only one page, don’t show any pagination. This is accomplished by the first if-clause:

{% if paginator.total_pages > 1 %}
...
{% endif %}

This means, only do the rest of the snippet if there’s more than 1 page.

Next we do the First link:

  <!-- first page link -->
  {% if paginator.page > 2 %}
    <a href="{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}" class="first">&laquo; First</a>
  {% endif %}

This one, as you recall from the rules above should only be displayed on page 3 and above. Also, I’ve added the CSS-class first in order to hide it for desktop apps.

The previous page link that should only be displayed if there is a previous page has the following code:

  <!-- previous page link -->
  {% if paginator.previous_page %}
    <a href="{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}">&laquo; Prev</a>
  {% endif %}

This link needs no class since it should be displayed regardless of on what device we’re displaying the page.

I’m now going to jump to the Next link in order to save the juciest parts to last.

   <!-- next page link -->
  {% if paginator.next_page %}
    <a href="{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}">Next &raquo;</a>
  {% endif %}

This one is also a link that should be displayed regardless of platform, so no class.

Now for the real complication. The direct page links.

I have decided to use two assigned variables first_page and last_page to determine where to start and stop generating page links. They are assigned according to the rules above:

  <!-- direct page links -->
  {% if paginator.page > 6 %}
    {% assign first_page = paginator.page | minus: 5 %}
  {% else %}
    {% assign first_page = 1 %}
  {% endif %}
  
  {% assign last_page = first_page | plus: 9 %}
  {% if last_page > paginator.total_pages %}
    {% assign last_page = paginator.total_pages %}
    {% assign first_page = last_page | minus: 9 %}
    {% if 1 > last_page %}
      {% assign last_page = 1 %}
    {% endif %}
  {% endif %}

first_page is set to 1 when we’re on page 1-6, and on page 7 and above, it’s set to paginator.page - 5 (current page - 5).

last_page is set to paginator.page + 9. If this number is larger than the total numner of pages (paginator.total_pages), we set last page to the last page number and the first page to last page - 9. If that value in turn is less than 1 (or, as I wrote the code to avoid some hideous syntax highlighting bugs, if 1 is larger than last page) we have less than ten pages and set the first page to 1.

After we’re done assigning the page number of the first and last page, we can render the page links:

  {% for page in (first_page..last_page) %}
    {% if page == paginator.page %}
      <span class="direct-link">{{ page }}</span>
    {% elsif page == 1 %}
      <a href="{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}" class="direct-link">{{ page }}</a>
    {% else %}
      <a href="{{ site.paginate_path | prepend: site.baseurl | replace: '//', '/' | replace: ':num', page }}" class="direct-link">{{ page }}</a>
    {% endif %}
  {% endfor %}

This code loops from first_page to last_page. If we’re on the current page (paginator.page) we only write the page number, otherwise we create a link to the page.

Since we have to have the first page as a single index.html file in the root of the site, while the other index-files can be located in a “page”-folder, we have to treat links for page one with special care. That is what the {% elsif page == 1 %} clause is for.

Finally we create the rest of the direct page links. This part of the code is dependent on the site.paginate_path variable, and that it contains the text :num where the page number should go.

The default value for this variable is /page:num/, so leaving it out of your _config.yml will still generate working pagination.

CSS

Now we’re almost there, the only thing left is to code the CSS.

The theme I’m using has the width settings for different devices set to less than 600 px for mobile, and 600 to 768 for tablet and above 768 for desktops.

Unfortunately your theme’s CSS might use other size settings, so you might want to adjust the following CSS accordingly, or not. Test and figure it out!

We design the CSS for mobiles as default and then use @media queries for tablets and desktops.

Put the following code in one of your theme’s CSS-file:

/* pagination */
.pagination {
	text-align: center;
	font-size: 130%;
}
	
.pagination .direct-link {
	display: none;
}

@media only screen and (min-width: 600px) {
	/* For tablets: */
	.pagination {
		font-size: 120%;
	}
}
	
@media only screen and (min-width: 768px) {
	/* For desktop: */
	.pagination {
		font-size: 100%;
	}
	.pagination .first {
		display: none;
	}
	.pagination .direct-link {
		display: inline;
	}
}

The only relevant parts to this discussion is the display: settings. The rest is up to how you want to design your pagination.