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.
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.
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> </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> </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">…</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">…</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"> <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"> <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--offset
class.
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> </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> </a>
</li>
...
<!-- Arrow: Next Page. -->
<li class="icon-next">
<a class="pagination-next hoverable" href="/pages/blog-6"
rel="next"> <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"> <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:
When the page size hits +tablet
size,
the pagination would looks like this below:
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.
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:
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>
<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>
</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>
<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>
</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">…</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">…</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">
<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>
<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">
<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>
<span class="fas fa-step-forward"></span>
</a>
{% endunless %}
</li>
</ul>
{% endif %}
</nav>
Small Animated Preview
I think this is all for now.
What is Next?
Consider continue reading [ Jekyll Bulma - Blog Post ].
Thank you for reading.