Where to Discuss?

Local Group

Preface

Goal: Bringing responsive pagination, using mobile first.

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.


1: Source

I respect copyright. The code below inspired by:

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


2: Prepare

Preview: General

This is the complete version.

Hugo Pagination: Responsive Animation

Layout: List

As usual, change the script to use responsive partial.

  {{ $paginator := .Paginate (where .Site.Pages "Type" "post") }}
  {{ partial "pagination-responsive.html" (dict "p" $paginator "s" .Scratch) }}

Partial: Minimal Pagination Code

You should have this artefact, before you begin.

<nav aria-label="Page navigation">
  {{ $s := .s }}
  {{ $p := .p }}

  {{ if gt $p.TotalPages 1 }}
  <ul class="pagination justify-content-center">

  ...

  </ul>
  {{ end }}

</nav>

SASS: Main

@import
  // taken from bootstrap
  // ...

  // variables
    "bootstrap/functions",
    "variables",
    "bootstrap/variables",
    "bootstrap/mixins/breakpoints",

  // custom
  // ...
    "pagination"
;

SASS: Custom Pagination

I’m using Bootstrap 4 grid breakpoints.

@include media-breakpoint-up(md) {
  ...
}

3: Navigation: HTML Class

The Final Result.

Consider have a look at the image below.

Hugo Pagination: Responsive 1

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

  <ul class="pagination justify-content-center">
      <li class="page-item blog_previous">...</li>
      <li class="page-item first">...</li>
      <li class="pages-indicator first disabled">...</li>
      <li class="page-item pagination--offset-2">...</li>
      <li class="page-item pagination--offset-1">...</li>
      <li class="page-item pagination--offset-0 active ">...</li>
      <li class="page-item pagination--offset-1">...</li>
      <li class="page-item pagination--offset-2">...</li>
      <li class="pages-indicator last disabled">...</li>
      <li class="page-item last">...</li>
      <li class="page-item blog_next">...</li>
  </ul>

Middle Pagination

All you need to care is, only these lines.

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

Partial: Pagination Code Skeleton

As usual, the skeleton, to show the complexity.

<nav aria-label="Page navigation">
  {{ $s := .s }}
  {{ $p := .p }}

  {{ if gt $p.TotalPages 1 }}
  <ul class="pagination justify-content-center">

    <!-- Variable Initialization. -->

    <!-- Previous Page. -->
    <!-- First Page. -->
    <!-- Early (More Pages) Indicator. -->

    {{- range $p.Pagers -}}
      {{ $s.Set "page_number_flag" false }}
      {{ $s.Set "page_offset" false }}

      <!-- Complex page numbers. -->
      {{ if gt $p.TotalPages $max_links }}
        <!-- Lower limit pages. -->
        <!-- Upper limit pages. -->
        <!-- Middle pages. -->

      <!-- Simple page numbers. -->
      {{ else }}.
        ...
      {{ end }}
      
      <!-- Calculate Offset Class. -->

      <!-- Show Pager. -->
      {{- if eq ($s.Get "page_number_flag") true -}}
      ...
      {{- end -}}
    {{ end }}

    <!-- Late (More Pages) Indicator. -->
    <!-- Last Page. -->
    <!-- Next Page. -->

  </ul>
  {{ end }}

</nav>

Notice the new variable called page_offset.

Partial: Pagination Code: Long Version

Before you begin, consider copy-and-paste from our last tutorial.

Calculate Offset Value

It is just a matter of difference, between current page and pager.

      <!-- Calculate Offset Class. -->
      {{- if eq ($s.Get "page_number_flag") true -}}
          {{ $s.Set "page_offset" (sub .PageNumber $p.PageNumber) }}
      {{- end -}}

Calculate Offset Class

We require to calculate absolute value, turn the negative value to positive. The issue is that Hugo does not come with abs() function, so the next part is a little bit cryptic.

      <!-- Calculate Offset Class. -->
      {{- if eq ($s.Get "page_number_flag") true -}}
          {{ $s.Set "page_offset" (sub .PageNumber $p.PageNumber) }}

          {{ $s.Set "page_offset_class" "" }}
          {{- if ge ($s.Get "page_offset") 0 -}}
              {{ $s.Set "page_offset_class" (print "pagination--offset-" ($s.Get "page_offset") ) }}
          {{- else -}}
              {{ $s.Set "page_offset_class" (print "pagination--offset" ($s.Get "page_offset") ) }}
          {{- end -}}
      {{- end -}}

Using Offset Class

All we need is just adding the offset class.

      <li class="page-item {{ $s.Get "page_offset_class" }}">
        ...
      </li>

The real code, is also not very simple.

      <!-- Show Pager. -->
      {{- if eq ($s.Get "page_number_flag") true -}}
      <li class="page-item{{ if eq $pagenumber .PageNumber }} active{{ end }} {{ $s.Get "page_offset_class" }}">
        {{ if not (eq $pagenumber .PageNumber) }} 
          <a href="{{ .URL }}" class="page-link">{{ .PageNumber }}</a>
        {{ else }}
          <span class="page-link page-item">{{ .PageNumber }}</span>
        {{ end }}
      </li>
      {{- end -}}

Combined Code

      {{- if eq ($s.Get "page_number_flag") true -}}
      <!-- Calculate Offset Class. -->
        {{ $s.Set "page_offset" (sub .PageNumber $p.PageNumber) }}

        {{ $s.Set "page_offset_class" "" }}
        {{- if ge ($s.Get "page_offset") 0 -}}
          {{ $s.Set "page_offset_class" (print "pagination--offset-" ($s.Get "page_offset") ) }}
        {{- else -}}
          {{ $s.Set "page_offset_class" (print "pagination--offset" ($s.Get "page_offset") ) }}
        {{- end -}}

      <!-- Show Pager. -->
      <li class="page-item{{ if eq $pagenumber .PageNumber }} active{{ end }} {{ $s.Get "page_offset_class" }}">
        {{ if not (eq $pagenumber .PageNumber) }} 
          <a href="{{ .URL }}" class="page-link">{{ .PageNumber }}</a>
        {{ else }}
          <span class="page-link page-item">{{ .PageNumber }}</span>
        {{ end }}
      </li>
      {{- end -}}

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


4: Responsive: 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:

Hugo Pagination: Responsive 1

Hugo Pagination: Responsive 2

Hugo Pagination: Responsive 3

Hugo Pagination: Responsive 4

Hugo Pagination: Responsive 5

Hugo Pagination: Responsive 6

SASS: Bootstrap Grid Breakpoint Variables.

Bootstrap 4 grid breakpoints are defined as below.

  • themes/tutor-05/sass/bootstrap/_variables.scss
// Grid breakpoints
//
// Define the minimum dimensions at which your layout will change,
// adapting to different screen sizes, for use in media queries.

$grid-breakpoints: (
  xs: 0,
  sm: 576px,
  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) {}  
  @include media-breakpoint-up(sm) {}
  @include media-breakpoint-up(md) {}
  @include media-breakpoint-up(lg) {}
  @include media-breakpoint-up(xl) {}
}

SASS: Using Bootstrap Breakpoint: Simple

We can fill any rules, as below:

@include media-breakpoint-up(md) {

  .blog_previous {
    span.page-link:after,
    a.page-link:after {
      content: " previous"
    }
  }

  .blog_next {
    span.page-link:before,
    a.page-link:before {
      content: "next "
    }
  }

}

SASS: Using Bootstrap Breakpoint: Pagination Offset

We can fill any rules, inside pagination class as below:

ul.pagination {

  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) {
  }
  
  @include media-breakpoint-up(sm) {
    li.pagination--offset-1,
    li.pagination--offset-2 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(md) {
    li.pagination--offset-3,
    li.pagination--offset-4 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(lg) {
    li.pagination--offset-5,
    li.pagination--offset-6,
    li.pagination--offset-7 {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(xl) {
  }

}

SASS: Responsive Indicator

You can also add CSS rules for indicator.

ul.pagination {

  li.first,
  li.last,
  li.pages-indicator {
    display: none;
  }

  @include media-breakpoint-up(xs) {
  }
  
  @include media-breakpoint-up(sm) {
    li.pages-indicator {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(md) {
    li.first,
    li.last {
      display: inline-block;
    }
  }

  @include media-breakpoint-up(lg) {
  }

  @include media-breakpoint-up(xl) {
  }

}

SASS: Enhanced Bootstrap Breakpoint

If you desire smoother transition effect, you can create your own breakpoint, beyond bootstrap.

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

And later, add css rule as code below:

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

SASS: Complete Code

Now you can have the complete code as below:

@include media-breakpoint-up(md) {

  .blog_previous {
    span.page-link:after,
    a.page-link:after {
      content: " previous"
    }
  }

  .blog_next {
    span.page-link:before,
    a.page-link:before {
      content: "next "
    }
  }

}

$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.first,
  li.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.first,
    li.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) {
  }

}

5: Summary

You can have a look at our complete code here:

We will have to complete the code later, in the next article. This has been a long article. We still need a few changes, for screen reader.


What is Next ?

Looks good right? This kitten, is still with me. Just like the kitten, do not get rest yet! Our pagination tutorial still have some materials to go.

adorable kitten

There are, some interesting topic about Pagination in Hugo. Consider continue reading [ Hugo - Pagination - Screen Reader ].

Thank you for reading.