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 Bootstrap 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

// Import partials from `sass_dir` (defaults to `sass/css`)
@import 
  // Heeyeun's Open Color
  "open-color/_open-color-variables",

  // Bootstrap Related
  "../bootstrap/functions",
  "variables",
  "../bootstrap/variables",
  "../bootstrap/mixins/breakpoints",

  // Tailor Made
  "main/layout-page",
  "main/layout-content",
  "main/logo",
  "main/decoration",
  "main/sticky-footer",
  "main/list",
  "main/pagination",
  
  "post/content",
  "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 aria-label="Page navigation">
  <ul class="pagination justify-content-center">

      <!-- Arrow: First Page. -->
      <li class="page-item icon-first">
        <a class="page-link hoverable" href="/pages" rel="first">
          <i data-feather="chevrons-left"></i></a>
      </li>

      <!-- Arrow: Previous Page. -->
      <li class="page-item icon-previous">
        <a class="page-link hoverable" href="/pages/blog-4" rel="prev">
          <i data-feather="chevron-left"></i></a>
      </li>

      <!-- First Page. -->
      <li class="page-item first">
        <a class="page-link hoverable" href="/pages">1</a>
      </li>

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

      <!-- Page numbers. -->
      <li class="page-item pagination--offset-2">
          <a class="page-link hoverable" href="/pages/blog-3">3</a>
      </li>
      <li class="page-item pagination--offset-1">
          <a class="page-link hoverable" href="/pages/blog-4">4</a>
      </li>
      <li class="page-item active pagination--offset-0">
          <span class="page-link">5</span>
      </li>
      <li class="page-item pagination--offset-1">
          <a class="page-link hoverable" href="/pages/blog-6">6</a>
      </li>
      <li class="page-item pagination--offset-2">
          <a class="page-link hoverable" href="/pages/blog-7">7</a>
      </li>

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

      <!-- Last Page. -->
      <li class="page-item last">
        <a class="page-link hoverable" href="/pages/blog-9">9</a>
      </li>

      <!-- Arrow: Next Page. -->
      <li class="page-item icon-next">
        <a class="page-link hoverable" href="/pages/blog-6" rel="next">
          <i data-feather="chevron-right"></i></a>
      </li>

      <!-- Arrow: Last Page. -->
      <li class="page-item icon-last">
        <a class="page-link hoverable" href="/pages/blog-9" rel="last">
          <i data-feather="chevrons-right"></i></a>
      </li>

  </ul>
</nav>

Middle Pagination

All you need to care is, only these lines.

  <!-- Page numbers. -->
  <li class="page-item pagination--offset-2">...</li>
  <li class="page-item pagination--offset-1">...</li>
  <li class="page-item active pagination--offset-0">...</li>
  <li class="page-item pagination--offset-1">...</li>
  <li class="page-item 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.

<nav aria-label="Page navigation">
  {% if total_pages > 1 %}
  <ul class="pagination justify-content-center">
    <!-- variable initialization -->

    <!-- Arrow: First Page. -->
    <!-- Arrow: Previous Page. -->

    {% if total_pages > link_max %}
      <!-- First Page. -->
      <!-- 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. -->
    {% endif %}

    <!-- Arrow: Next Page. -->
    <!-- Arrow: Last Page. -->
  </ul>
  {% endif %}
</nav>

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="page-item
             {% if page_cursor == page_current %}active{% endif %}
             pagination--offset-{{ diff_offset }}">
    ...
  </li>
{% endif %}

Combined Code

      <!-- Show Pager. -->
      {% if page_current_flag == true %}
      <li class="page-item
                 {% if page_cursor == page_current %}active{% endif %}
                 pagination--offset-{{ diff_offset }}">
        {% if page_cursor == page_current %} 
          <span class="page-link">
            {{ page_cursor }}
          </span>
        {% else %}

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

          <a class="page-link hoverable"
             href="{{ p_link }}"
           >{{ 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="page-item icon-first">
        <a class="page-link hoverable" href="/pages" rel="first">
          <i data-feather="chevrons-left"></i></a>
      </li>

      <!-- Arrow: Previous Page. -->
      <li class="page-item icon-previous">
        <a class="page-link hoverable" href="/pages/blog-4" rel="prev">
          <i data-feather="chevron-left"></i></a>
      </li>

      ...

      <!-- Arrow: Next Page. -->
      <li class="page-item icon-next">
        <a class="page-link hoverable" href="/pages/blog-6" rel="next">
          <i data-feather="chevron-right"></i></a>
      </li>

      <!-- Arrow: Last Page. -->
      <li class="page-item icon-last">
        <a class="page-link hoverable" href="/pages/blog-9" rel="last">
          <i data-feather="chevrons-right"></i></a>
      </li>

  </ul>
</nav>

SASS: Bootstrap Breakpoints

Consider setting, using mobile first:

.icon-first,
.icon-last {
  display: none;
 }

@include media-breakpoint-up(sm) {
  .icon-first,
  .icon-last {
    display: block;
   }
}

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

Jeykll Pagination: Responsive - Mobile - Hide

When the page size hits small sm 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.

@include media-breakpoint-up(md) {
  .icon-previous :after  { content: " Previous" }
  .icon-next     :before { content: "Next " }
  .icon-first    :after  { content: " First" }
  .icon-last     :before { content: "Last " }

  .icon-previous svg,
  .icon-next svg,
  .icon-first svg,
  .icon-last svg {
    display: none;
   }
}

When the page width hit medium md 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: Bootstrap Custom Breakpoint Variables.

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

$grid-breakpoints-custom: (
  xs:  0,
  xs2: 320px,
  xs3: 400px,
  xs4: 480px,
  sm:  576px,
  sm2: 600px,
  md:  768px,
  lg:  992px,
  xl:  1200px
) !default;

SASS: Bootstrap Breakpoint Skeleton

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

ul.pagination {
  @include media-breakpoint-up(xs,  $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(xs2, $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(xs3, $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(xs4, $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(sm,  $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(sm2, $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(md,  $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(lg,  $grid-breakpoints-custom) { ... }
  @include media-breakpoint-up(xl,  $grid-breakpoints-custom) { ... }
}

SASS: Using Bootstrap Breakpoint: Mobile

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

ul.pagination {
  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;
  }
}

SASS: Using Custom Breakpoint: Pagination Offset

And then continue with pagination--offset setting, for each custom breakpoints.

ul.pagination {
  ...

  @include media-breakpoint-up(xs, $grid-breakpoints-custom) {
  }
  
  @include media-breakpoint-up(xs2, $grid-breakpoints-custom) {
    li.pages-indicator {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(xs3, $grid-breakpoints-custom) {
    li.pagination--offset-1 {
      display: inline-block;
    }
  }
  
  @include media-breakpoint-up(xs4, $grid-breakpoints-custom) {
    li.pagination--offset-2 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(sm, $grid-breakpoints-custom) {
    li.icon-first,
    li.icon-last,
    li.pagination--offset-3 {
      display: inline-block;
    }
  }
  
  @include media-breakpoint-up(sm2, $grid-breakpoints-custom) {
    li.pagination--offset-4 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(md, $grid-breakpoints-custom) {
    li.pagination--offset-5,
    li.pagination--offset-6 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(lg, $grid-breakpoints-custom) {
    li.pagination--offset-7 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(xl, $grid-breakpoints-custom) {
  }
}

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

SASS: Complete Code

Now you can have the complete code as below:

.icon-first,
.icon-last {
  display: none;
 }

@include media-breakpoint-up(sm) {
  .icon-first,
  .icon-last {
    display: block;
   }
}

@include media-breakpoint-up(md) {
  .icon-previous :after  { content: " Previous" }
  .icon-next     :before { content: "Next " }
  .icon-first    :after  { content: " First" }
  .icon-last     :before { content: "Last " }

  .icon-previous svg,
  .icon-next svg,
  .icon-first svg,
  .icon-last svg {
    display: none;
   }
}

$grid-breakpoints-custom: (
  xs:  0,
  xs2: 320px,
  xs3: 400px,
  xs4: 480px,
  sm:  576px,
  sm2: 600px,
  md:  768px,
  lg:  992px,
  xl:  1200px
) !default;

ul.pagination {

  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;
  }

  @include media-breakpoint-up(xs, $grid-breakpoints-custom) {
  }
  
  @include media-breakpoint-up(xs2, $grid-breakpoints-custom) {
    li.pages-indicator {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(xs3, $grid-breakpoints-custom) {
    li.pagination--offset-1 {
      display: inline-block;
    }
  }
  
  @include media-breakpoint-up(xs4, $grid-breakpoints-custom) {
    li.pagination--offset-2 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(sm, $grid-breakpoints-custom) {
    li.icon-first,
    liicon-last,
    li.pagination--offset-3 {
      display: inline-block;
    }
  }
  
  @include media-breakpoint-up(sm2, $grid-breakpoints-custom) {
    li.pagination--offset-4 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(md, $grid-breakpoints-custom) {
    li.pagination--offset-5,
    li.pagination--offset-6 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(lg, $grid-breakpoints-custom) {
    li.pagination--offset-7 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(xl, $grid-breakpoints-custom) {
  }

}

6: Screenreader

Accessability Class

I just follow Bootstrap guidance:

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

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

Aria Label

Instead of class, we can also utilize aria-label. but I do not bother to implement it in my bootstrap code.

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 aria-label="Page navigation">

  {% if total_pages > 1 %}
  <ul class="pagination justify-content-center">
  
    {% 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 %}

    <!-- First Page. -->
    {% unless paginator.page == 1 %}
      {% assign p_first = paginate_root 
                        | prepend: site.baseurl %}
      <li class="page-item icon-first ">
        <a class="page-link hoverable" href="{{ p_first }}" 
           rel="first">
          <i data-feather="chevrons-left"></i>
          <span class="sr-only">First</span>
        </a>
      </li>
    {% else %}
      <li class="page-item icon-first disabled">
        <span class="page-link">
          <i data-feather="chevrons-left"></i></span>
      </li>
    {% endunless %}

    <!-- Previous Page. -->
    {% if paginator.previous_page %}
      {% assign p_prev = paginator.previous_page_path
                       | prepend: site.baseurl %}
      <li class="page-item icon-previous">
        <a class="page-link hoverable" 
           href="{{ p_prev }}"
           rel="prev">
          <i data-feather="chevron-left"></i>
          <span class="sr-only">Previous</span>
        </a>
      </li>
    {% else %}
      <li class="page-item icon-previous disabled">
        <span class="page-link">
          <i data-feather="chevron-left"></i></span>
      </li>
    {% endif %}

    {% if total_pages > link_max %}
      <!-- First Page. -->
      {% if lower_offset > 1 %}
        <li class="page-item first">
          <a class="page-link hoverable"
             href="{{ p_first }}">
            <span class="sr-only">Page 1</span>
          1</a>
        </li>
      {% endif %}

      <!-- Early (More Pages) Indicator. -->
      {% if lower_offset > lower_indicator %}
        <li class="pages-indicator first disabled">
          <span class="page-link">&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="page-item
                 {% if page_cursor == page_current %}active{% endif %}
                 pagination--offset-{{ diff_offset }}">
        {% if page_cursor == page_current %} 
          <span class="page-link">
            {{ page_cursor }}
          </span>
        {% 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 %}
            {% assign p_link = p_link 
                             | prepend: site.baseurl %}
          {% endif %}
          {% endcapture %}

          <a class="page-link hoverable"
             href="{{ p_link }}"
           ><span class="sr-only">Page {{ 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 last disabled">
          <span class="page-link">&hellip;</span>
        </li>
      {% endif %}

      <!-- Last Page. -->
      {% if upper_offset < total_pages %}
        <li class="page-item last">
          <a class="page-link hoverable" 
             href="{{ p_last }}"
          ><span class="sr-only">Page {{ total_pages }}</span>
          {{ total_pages }}</a>
        </li>
      {% endif %}
    {% endif %}

    <!-- Next Page. -->
    {% if paginator.next_page %}
      {% assign p_next = paginator.next_page_path
                       | prepend: site.baseurl %}
      <li class="page-item icon-next">
        <a class="page-link hoverable" 
           href="{{ p_next }}" 
           rel="next">
          <i data-feather="chevron-right"></i>
          <span class="sr-only">Next</span>
        </a>
      </li>
    {% else %}
      <li class="page-item icon-next disabled">
        <span class="page-link">
          <i data-feather="chevron-right"></i></span>
      </li>
    {% endif %}

    <!-- Last Page. -->
    {% unless paginator.page == total_pages %}
      {% assign p_last = site.paginate_path
                       | relative_url 
                       | replace: ':num', total_pages 
                       | prepend: site.baseurl %}
      <li class="page-item icon-last">
        <a class="page-link hoverable" href="{{ p_last }}" 
           rel="last">
          <i data-feather="chevrons-right"></i>
          <span class="sr-only">Last</span>
        </a>
      </li>
    {% else %}
      <li class="page-item icon-last disabled">
        <span class="page-link">
          <i data-feather="chevrons-right"></i></span>
      </li>
    {% endunless %}
  </ul>
  {% endif %}
</nav>

Small Animated

You can have different pagination arrangement as you want.

Jekyll Pagination: Responsive Animation

Or try to mess it up a little bit:

Jekyll Pagination: Alternate Responsive Animation

I think this is all for now.


What is Next?

Consider continue reading [ Jekyll Bootstrap - Blog Post ].

Thank you for reading.