ssg  
Article Series

Jekyll in General

Jekyll Plain

Where to Discuss?

Local Group

Preface

Goal: Bringing responsive pagination, using mobile first.

Source Code

This article use tutor-11 theme. We will create it step by step.


1: Source

Original Source

I respect copyright. The code below inspired by:

I made a slight modification. But of course the logic remain the same.

Of course, this article is talking about SASS

Altered Source

Coder will feel like home

While writing this CSS code, I can’t claim myself as a developer. Because CSS is not a programming language.

But weird that, responsive design has their own logic. So yeah, I have to code, a little.


2: Prepare

This step is required.

Preview: General

This is what we want to achieve in this tutorial.

Jekyll Bulma Pagination: 05 Responsive

Layout: Liquid: Blog

Consider use pagination/05-responsive partial, in blog.html.

---
layout: columns-single
---

{% include pagination-v1/05-responsive.html %}

{% assign posts = paginator.posts %}
{% include index/blog-list.html %}

SASS: Main

// Bulma Related
@import "bulma/initial-variables"
@import "vendors/bulma/utilities/initial-variables"
@import "vendors/bulma/utilities/functions"
@import "vendors/bulma/utilities/derived-variables"
@import "vendors/bulma/utilities/mixins"

// Materialize Related
@import "materialize/color-variables"

// Tailor Made
@import "main/layout-page"
@import "main/layout-content"
@import "main/decoration"
@import "main/pagination"
@import "main/list"

@import "post/content"
@import "post/navigation"

3: Navigation: HTML Class

HTML: The Final Result

Consider have a look at the image below.

Jekyll Pagination: Responsive Animation

The HTML that we want to achieve is similar as below.

<nav class="pagination is-small is-centered" ...>

    <ul class="pagination-list">

      <!-- Arrow: First Page. -->
      <li class="icon-first">
        <a class="pagination-previous hoverable" href="/pages"
           rel="first"><span class="fas fa-step-backward"></span>&nbsp;</a>
      </li>

      <!-- Arrow: Previous Page. -->
      <li class="icon-previous">
        <a class="pagination-previous hoverable" href="/pages/blog-4" 
           rel="prev"><span class="fas fa-backward"></span>&nbsp;</a>
      </li>

      <!-- First Page. -->
      <li class="first"><a class="pagination-link" ...>1</a></li>

      <!-- Early (More Pages) Indicator. -->
      <li class="pages-indicator first"><span class="pagination-ellipsis">&hellip;</span></li>

      <!-- Page numbers. -->
      <li class="pagination--offset-2"><a class="pagination-link" ...>3</a></li>
      <li class="pagination--offset-1"><a class="pagination-link" ...>4</a></li>
      <li class="pagination--offset-0"><a class="pagination-link is-current" ...>5</a></li>
      <li class="pagination--offset-1"><a class="pagination-link" ...>6</a></li>
      <li class="pagination--offset-2"><a class="pagination-link" ...>7</a></li>

      <!-- Late (More Pages) Indicator. -->
      <li class="pages-indicator last"><span class="pagination-ellipsis">&hellip;</span></li>

      <!-- Last Page. -->
      <li class="last"><a class="pagination-link" ...>9</a></li>

      <!-- Arrow: Next Page. -->
      <li class="icon-next">
        <a class="pagination-next hoverable" href="/pages/blog-6"
           rel="next">&nbsp;<span class="fas fa-forward"></span></a>
      </li>

      <!-- Arrow: Last Page. -->
      <li class="icon-last">
        <a class="pagination-next hoverable" href="/pages/blog-9"
           rel="last">&nbsp;<span class="fas fa-step-forward"></span></a>
      </li>
    </ul>

</nav>

Middle Pagination

All you need to care is, only these lines.

  <!-- Page numbers. -->
  <li class="pagination--offset-2">...</li>
  <li class="pagination--offset-1">...</li>
  <li class="pagination--offset-0">...</li>
  <li class="pagination--offset-1">...</li>
  <li class="pagination--offset-2">...</li>

Our short term goal is, to put the pagination--offsetclass.

Partial: Responsive Skeleton

As usual, the skeleton, to show the complexity.

{% if totalPages > 1 %}
<nav class="pagination is-small is-centered" ...>
  <ul class="pagination-list">
    <!-- variable initialization -->

    <!-- First Page (arrow). -->
    <!-- Previous Page (arrow). -->

    {% if total_pages > link_max %}
      <!-- First Page (number = 1). -->
      <!-- Early (More Pages) Indicator. -->
    {% endif %}

    <!-- Page numbers. -->
    {% for page_cursor in (1..total_pages) %}
      <!-- Flag Calculation -->

      {% if page_current_flag == true %}
        <!-- Calculate Offset Class. -->
        {% assign diff_offset = page_cursor | minus: page_current | abs %}

        <!-- Show Pager. -->
        ...
      {% endif %}
    {% endfor %}

    {% if total_pages > link_max %}
      <!-- Late (More Pages) Indicator. -->
      <!-- Last Page (number = total page). -->
    {% endif %}

    <!-- Next Page (arrow). -->
    <!-- Last Page (arrow). -->
  </ul>
</nav>
{% endif %}

Calculate Offset Value

Notice this part:

{% if page_current_flag == true %}
  <!-- Calculate Offset Class. -->
  {% assign diff_offset = page_cursor | minus: page_current | abs %}
{% endif %}

Notice the new variable called diff_offset.

Using Offset Class

All we need is just adding the offset class.

{% if page_current_flag == true %}
  <!-- Show Pager. -->
  <li class="pagination--offset-{{ diff_offset }}">
    ...
  </li>
{% endif %}

Combined Code

      <!-- Show Pager. -->
      {% if page_current_flag == true %}
      <li class="pagination--offset-{{ diff_offset }}">
        {% if page_cursor == page_current %} 
          <a class="pagination-link is-current {{ color_main }}"
             aria-label="Page {{ page_cursor }}">
            {{ page_cursor }}
          </a>
        {% else %}

          <!-- p_link calculation -->
          ...

          <a class="pagination-link hoverable"
             href="{{ p_link }}"
             aria-label="Goto page_cursor {{ page_cursor }}">
            {{ page_cursor }}
          </a>
        {% endif %}
      </li>
      {% endif %}

That is all. Now that the HTML part is ready, we should go on, by setting up the responsive breakpoints using SCSS.


4: Responsive: Mobile First

HTML: Render Preview

As written above, we have have this html structure, to create arrow icon.

<nav aria-label="Page navigation">
  <ul class="pagination justify-content-center">
      <!-- Arrow: First Page. -->
      <li class="icon-first">
        <a class="pagination-previous hoverable" href="/pages"
           rel="first"><span class="fas fa-step-backward"></span>&nbsp;</a>
      </li>

      <!-- Arrow: Previous Page. -->
      <li class="icon-previous">
        <a class="pagination-previous hoverable" href="/pages/blog-4" 
           rel="prev"><span class="fas fa-backward"></span>&nbsp;</a>
      </li>

      ...

      <!-- Arrow: Next Page. -->
      <li class="icon-next">
        <a class="pagination-next hoverable" href="/pages/blog-6"
           rel="next">&nbsp;<span class="fas fa-forward"></span></a>
      </li>

      <!-- Arrow: Last Page. -->
      <li class="icon-last">
        <a class="pagination-next hoverable" href="/pages/blog-9"
           rel="last">&nbsp;<span class="fas fa-step-forward"></span></a>
      </li>
  </ul>
</nav>

SASS: Bulma Breakpoints

Consider setting, using mobile first:

// Responsiveness

ul.pagination-list
  li.icon-first,
  li.icon-last
    display: none
  +tablet
    li.icon-first,
    li.icon-last
      display: inline-block

Normally, the pagination on mobile screen, would looks like this figure below:

Jeykll Pagination: Responsive - Mobile - Hide

When the page size hits +tablet size, the pagination would looks like this below:

Jeykll Pagination: Responsive - Small - Icon

SASS: Text and Icon

Consider further setting. For wider area, we can apply text instead of icon.

+tablet
  li.icon-previous a:after
    content: " Previous"
  li.icon-next a:before
    content: "Next "
  li.icon-first a:after
    content: " First"
  li.icon-last a:before
    content: "Last "
  li.icon-previous a span.fas,
  li.icon-next a span.fas,
  li.icon-first a span.fas,
  li.icon-last a span.fas
    display: none

When the page width hit medium tablet size, the icon will dissapear, and replaced with text, set in CSS above.

Jeykll Pagination: Responsive - Medium - Text


5: Responsive: Custom Breakpoints

Responsive is easy if you understand the logic.

It is all about breakpoints.

Preview: Each Breakpoint

Consider again, have a look at the animation above, frame by frame. We have at least five breakpoint as six figures below:

Jekyll Pagination: Responsive 1

Jekyll Pagination: Responsive 2

Jekyll Pagination: Responsive 3

Jekyll Pagination: Responsive 4

Jekyll Pagination: Responsive 5

Jekyll Pagination: Responsive 6

Note that you can have different view arrangement as you want. You can see more in examples below.

SASS: Bulma Custom Breakpoint Variables.

I’m using custom breakpoint, instead of Bulma v4.x breakpoints.

// Breakpoint

$xs1: 0
$xs2: 320px
$xs3: 380px
$xs4: 440px
$sm1: 576px
$sm2: 600px
$md:  768px
$lg:  992px
$xl:  1200px

The name inspired by Bootsrap, but it has nothing do with Bootstrap.

SASS: Bulma Breakpoint Skeleton

With breakpoint above, we can setup css skeleton, with empty css rules.

ul.pagination-list
  +from($xs1)
  +from($xs2)
  +from($xs3)
  +from($xs4)
  +from($sm1)
  +from($sm2)
  +from($md)
  +from($lg)
  +from($xl)

SASS: Using Bulma Breakpoint: Mobile

We can fill initial rule, for mobile screen, as below:

ul.pagination-list
  li.pagination--offset-1,
  li.pagination--offset-2,
  li.pagination--offset-3,
  li.pagination--offset-4,
  li.pagination--offset-5,
  li.pagination--offset-6,
  li.pagination--offset-7
    display: none

SASS: Using Custom Breakpoint: Pagination Offset

We can fill any rules, inside pagination-list class as below: Such as pagination--offset setting, for each custom breakpoints.

// Responsiveness

ul.pagination-list
  ...
  +from($xs1)
  +from($xs2)
  +from($xs3)
    li.pagination--offset-1
      display: inline-block
  +from($xs4)
    li.pagination--offset-2
      display: inline-block
  +from($sm1)
    li.pagination--offset-3
      display: inline-block
  +from($sm2)
    li.pagination--offset-4
      display: inline-block
  +from($md)
    li.pagination--offset-5,
    li.pagination--offset-6
      display: inline-block
  +from($lg)
    li.pagination--offset-7
      display: inline-block
  +from($xl)

This setup breakpoint is actually up to you. You may change to suit whatever you need

SASS: Responsive Indicator

You can also add CSS rules for indicator, and stuff.

// Responsiveness

ul.pagination-list
  li.icon-first,
  li.icon-last,
  li.pages-indicator
    display: none
  +from($xs1)
  +from($xs2)
    li.pages-indicator
      display: inline-block
  +from($sm1)
    li.icon-first,
    li.icon-last,

Short and simple.

SASS: Decoration

If you desire, a slight enhancement without breaking the original looks.

Hover is a good idea.

// hover color

ul.pagination-list li 
  a:hover
    background-color: map-get($yellow, 'lighten-2')
    color: #000
  a.is-current:hover
    background-color: map-get($grey, 'base') !important
    color: #fff

And so is gap margin.

// layout

nav.pagination
  margin-top: 10px
  margin-bottom: 10px

SASS: Complete Code

Now you can have the complete code as below:

+tablet
  li.icon-previous a:after
    content: " Previous"
  li.icon-next a:before
    content: "Next "
  li.icon-first a:after
    content: " First"
  li.icon-last a:before
    content: "Last "
  li.icon-previous a span.fas,
  li.icon-next a span.fas,
  li.icon-first a span.fas,
  li.icon-last a span.fas
    display: none

// Breakpoint

$xs1: 0
$xs2: 320px
$xs3: 380px
$xs4: 440px
$sm1: 576px
$sm2: 600px
$md:  768px
$lg:  992px
$xl:  1200px

// Responsiveness

ul.pagination-list
  li.icon-first,
  li.icon-last,
  li.pages-indicator
    display: none
  li.pagination--offset-1,
  li.pagination--offset-2,
  li.pagination--offset-3,
  li.pagination--offset-4,
  li.pagination--offset-5,
  li.pagination--offset-6,
  li.pagination--offset-7
    display: none
  +from($xs1)
  +from($xs2)
    li.pages-indicator
      display: inline-block
  +from($xs3)
    li.pagination--offset-1
      display: inline-block
  +from($xs4)
    li.pagination--offset-2
      display: inline-block
  +from($sm1)
    li.icon-first,
    li.icon-last,
    li.pagination--offset-3
      display: inline-block
  +from($sm2)
    li.pagination--offset-4
      display: inline-block
  +from($md)
    li.pagination--offset-5,
    li.pagination--offset-6
      display: inline-block
  +from($lg)
    li.pagination--offset-7
      display: inline-block
  +from($xl)

// hover color

ul.pagination-list li 
  a:hover
    background-color: map-get($yellow, 'lighten-2')
    color: #000
  a.is-current:hover
    background-color: map-get($grey, 'base') !important
    color: #fff

// layout

nav.pagination
  margin-top: 10px
  margin-bottom: 10px

6: Screenreader

Accessability Class

I just follow Bulma guidance:

To hidden content visually, you simply need to add is-sr-only.

<span class="is-sr-only">Hidden Content</span>

Alternatively you can use fontawesome class that is similar to bootstrap.

<span class="sr-only">Hidden Content</span>

This content can be read by screenreader. You can test using inspect element.

Aria Label

Instead of class, we can also utilize aria-label

        <a class="pagination-link is-current {{ color_main }}"
           aria-label="Page {{ page_cursor }}">
          <span class="is-sr-only">Page </span>{{ page_cursor }}
        </a>

Preview: General

There shoud be nomore preview, because this is screen reader.

This content can be read by screenreader. You can test using inspect element.

Our final code will be equipped with screenreader class.


7: Summary

Now the pagination tutorial is done.

Complete Code

You can have a look at our complete code here:

{% capture spaceless %}
  {% assign total_pages = paginator.total_pages %}
  {% assign color_main  = page.color_main  | default: layout.color_main %}
{% endcapture %}

<nav class="pagination is-small is-centered"
     role="navigation" aria-label="pagination">
  {% if total_pages > 1 %}

    {% capture spaceless %}
      <!--
        Pagination links 
        * https://glennmccomb.com/articles/how-to-build-custom-hugo-pagination/
      -->

      <!-- Get paginate_root from page in frontmatter -->
      {% assign paginate_root = page.paginate_root %}
      {% assign p_first = paginate_root 
                        | prepend: site.baseurl %}
      {% assign p_last  = site.paginate_path
                        | relative_url 
                        | replace: ':num', total_pages %}

      {% assign page_current  = paginator.page %}

      {% assign link_offset   = 2 %}  
      {% assign link_max      = link_offset   | times: 2 | plus: 1 %}

      {% assign limit_lower   = link_offset   | plus: 1 %}
      {% assign limit_upper   = total_pages   | minus: link_offset %}
  
      {% assign min_lower     = link_max %}  
      {% assign max_upper     = total_pages   | minus: link_max %}
    
      {% assign lower_offset  = page_current  | minus: link_offset %}  
      {% assign upper_offset  = page_current  | plus: link_offset %}  

      {% assign lower_indicator = 2 %}
      {% assign upper_indicator = total_pages | minus: 1 %}

    {% endcapture %}

    <ul class="pagination-list">

      <!-- First Page. -->
      <li class="icon-first">
      {% unless paginator.page == 1 %}
        <a class="pagination-previous hoverable"
           href="{{ p_first }}"
           rel="first">
          <span class="fas fa-step-backward"></span>&nbsp;
          <span class="is-sr-only">First</span>
        </a>
      {% else %}
        <a class="pagination-previous"
           title="This is the first page"
           disabled>
          <span class="fas fa-step-backward"></span>&nbsp;
        </a>
      {% endunless %}
      </li>

      <!-- Previous Page. -->
      <li class="icon-previous">
      {% if paginator.previous_page %}
        {% assign p_prev = paginator.previous_page_path
                         | prepend: site.baseurl %}
        <a class="pagination-previous hoverable"
           href="{{ p_prev }}" 
           rel="prev">
          <span class="fas fa-backward"></span>&nbsp;
          <span class="is-sr-only">Previous</span>
        </a>
      {% else %}
        <a class="pagination-previous"
           title="This is the first page"
           disabled>
          <span class="fas fa-backward"></span>&nbsp;
        </a>
      {% endif %}
      </li>

    {% if total_pages > link_max %}
      <!-- First Page. -->
      {% if lower_offset > 1 %}
      <li>
        <a class="pagination-link hoverable"
           href="{{ p_first }}"
           aria-label="Goto page_cursor 1">
          <span class="is-sr-only">Goto page_cursor </span>1
        </a>
      </li>
      {% endif %}

      <!-- Early (More Pages) Indicator. -->
      {% if lower_offset > lower_indicator %}
      <li class="pages-indicator">
        <span class="pagination-ellipsis">&hellip;</span>
      </li>
      {% endif %}
    {% endif %}

    <!-- Page numbers. -->
    {% for page_cursor in (1..total_pages) %}

      {% capture spaceless %}
        <!-- Flag Calculation -->
        {% assign page_current_flag = false %}

        {% if total_pages > link_max %}
        <!-- Complex page_cursor numbers. -->

          <!-- Lower limit pages. -->
          <!-- If the user is on a page_cursor which is in the lower limit.  -->
          {% if page_current <= limit_lower %}
            <!-- If the current loop page_cursor is less than max_links. -->
            {% if page_cursor <= min_lower %}
              {% assign page_current_flag = true %}
            {% endif %}

          <!-- Upper limit pages. -->
          <!-- If the user is on a page_cursor which is in the upper limit. -->
          {% elsif page_current >= limit_upper %}
            <!-- If the current loop page_cursor is greater than total pages minus $max_links -->
            {% if page_cursor > max_upper %}
              {% assign page_current_flag = true %}
            {% endif %}

          <!-- Middle pages. -->
          {% else %}
          
            {% if (page_cursor >= lower_offset) and (page_cursor <= upper_offset) %}
              {% assign page_current_flag = true %}
            {% endif %}

          {% endif %}

        {% else %}
        <!-- Simple page_cursor numbers. -->

          {% assign page_current_flag = true %}
        {% endif %}

        {% if page_current_flag == true %}
        <!-- Calculate Offset Class. -->
          {% assign diff_offset = page_cursor | minus: page_current | abs %}
        {% endif %}

      {% endcapture %}

      <!-- Show Pager. -->
      {% if page_current_flag == true %}
      <li class="pagination--offset-{{ diff_offset }}">
        {% if page_cursor == page_current %} 
        <a class="pagination-link is-current {{ color_main }}"
           aria-label="Page {{ page_cursor }}">
          <span class="is-sr-only">Page </span>{{ page_cursor }}
        </a>
        {% else %}

          {% capture spaceless %}
          {% if page_cursor == 1 %}
            {% assign p_link = p_first %}
          {% else %}
            {% assign p_link = site.paginate_path
                             | relative_url
                             | replace: ':num', page_cursor
                             | prepend: site.baseurl %}
          {% endif %}
          {% endcapture %}

        <a class="pagination-link hoverable"
           href="{{ p_link }}"
           aria-label="Goto page_cursor {{ page_cursor }}">
          <span class="is-sr-only">Goto page_cursor </span>{{ page_cursor }}
        </a>
        {% endif %}
      </li>
      {% endif %}

    {% endfor %}

    {% if total_pages > link_max %}
      <!-- Late (More Pages) Indicator. -->
      {% if upper_offset < upper_indicator %}
      <li class="pages-indicator">
        <span class="pagination-ellipsis">&hellip;</span>
      </li>
      {% endif %}

      <!-- Last Page. -->
      {% if upper_offset < total_pages %}
      <li>
        <a class="pagination-link hoverable" 
           href="{{ p_last }}"
           aria-label="Goto page_cursor {{ total_pages }}">
          <span class="is-sr-only">Goto page_cursor </span>{{ total_pages }}
        </a>
      </li>
      {% endif %}
    {% endif %}

      <!-- Next Page. -->
      <li class="icon-next">
      {% if paginator.next_page %}
        {% assign p_next = paginator.next_page_path
                         | prepend: site.baseurl %}
        <a class="pagination-next hoverable"
           href="{{ p_next }}"
           rel="next">&nbsp;
          <span class="fas fa-forward"></span>
          <span class="is-sr-only">Next</span>
        </a>
      {% else %}
        <a class="pagination-next"
           title="This is the last page"
           disabled>&nbsp;
          <span class="fas fa-forward"></span>
        </a>
      {% endif %}
      </li>

      <!-- Last Page. -->
      <li class="icon-last">
      {% unless paginator.page == total_pages %}
        <a class="pagination-next hoverable"
           href="{{ p_last }}"
           rel="last">&nbsp;
          <span class="fas fa-step-forward"></span>
          <span class="is-sr-only">Last</span>
        </a>
      {% else %}
        <a class="pagination-next"
           title="This is the last page"
           disabled>&nbsp;
          <span class="fas fa-step-forward"></span>
        </a>
      {% endunless %}
      </li>

    </ul>
  {% endif %}
</nav>

Small Animated Preview

Jekyll Pagination: Responsive Animation

I think this is all for now.


What is Next?

Consider continue reading [ Jekyll Bulma - Blog Post ].

Thank you for reading.