Pagination

Use pagination when a list or table is too long to scan comfortably as one page. The control belongs after the list by default. Add a compact control above the list when the dataset is large enough that avoiding a scroll back to the footer is useful.

When to use#

  • Use about 12 rows per page for standard editorial lists; increase the page size only when rows are very compact or the page is primarily a dense reference table
  • Place the control after the list by default; add the compact variant above the list sparingly — it is currently reserved for very long link indexes where scrolling back to the footer is costly
  • Do not mix Hugo pagination and client-side table pagination on the same dataset — pick one owner for the visible page: Hugo pagination for section indexes where each result is its own page and the browser should load a new URL for each page of results, client-side table pagination for one data table where filters, sorting, and visible rows all operate on the same loaded dataset
  • A page can contain both models only when they control different datasets — an article index can use Hugo pagination while an unrelated table on the same page uses client-side table pagination; avoid nesting one inside the other
  • Grouped lookup references — airline codes, country codes, international country calling codes — should usually avoid classic pagination, because an anchor index over grouped sections is better for lookup

Implementation#

For Hugo paginated lists, use the shared partial:

{{ partial "pagination.html" (dict "paginator" $paginator) }}

Use the compact variant above long lists:

{{ partial "pagination.html" (dict "paginator" $paginator "variant" "compact") }}
<nav class="pagination pagination-footer" aria-label="Pagination">
  <p class="pagination-summary">Showing 1 to 12 of 34 results</p>
  <ol class="pagination-pages">
    <li><span class="pagination-button pagination-button-icon pagination-button-disabled" aria-hidden="true">‹</span></li>
    <li><span class="pagination-button pagination-button-current" aria-current="page">1</span></li>
    <li><a class="pagination-button" href="#" aria-label="Go to page 2">2</a></li>
    <li><a class="pagination-button pagination-button-icon" href="#" aria-label="Next">›</a></li>
  </ol>
</nav>

Client-side table pagination has its own partial, data-page/table-pagination.html — separate from the Hugo-paginator-based partial above because it has no .Paginator object to render; it is JS-driven row show/hide, not server pagination. The host page sets data-page-size on its data-table-list container and includes the partial for the control markup; initTableLists in assets/js/site.js wires it and hides the control while all rows fit on one page:

<section data-table-list data-page-size="10">
  <!-- filter row and table -->
  {{ partial "data-page/table-pagination.html" (dict) }}
</section>

The partial’s class and ariaLabel keys are optional overrides; both default to the publications page’s original markup.

The Hugo partial is layouts/partials/pagination.html. It renders nothing when the paginator has a single page, computes the visible range and totals from the paginator, and windows the page numbers — past seven pages it keeps the first, the last, and the current page with the page on each side, inserting ellipses between. Labels come from i18n; the styling is the pagination-* classes in assets/css/site.css.

Options#

These belong to pagination.html:

OptionPurpose
paginatorThe Hugo paginator for the list (required)
variantfooter (default) with range summary and totals, or compact with page controls only

data-page/table-pagination.html takes two optional overrides instead:

OptionPurpose
classWrapper <nav> classes; defaults to the publications page’s own layout
ariaLabelLandmark label; defaults to the pagination i18n key

Variants#

The footer variant includes the visible range, total result count, previous/next controls, page numbers, and ellipses. The compact variant only includes page controls and should be right-aligned; use it sparingly as a top control above very long link indexes.

Interface manifest

Kind
Component
Category
Navigation
Status
Implemented
Implementation
Partial:layouts/partials/pagination.htmllayouts/partials/data-page/table-pagination.htmlCSS:assets/css/site.cssJavaScript:assets/js/site.jsi18n:paginationpaginationShowingpaginationGoToPagepreviousnext

Accessibility#

  • Both controls are labelled <nav> landmarks: the partial and the client-side control in the publications layout take their name from the pagination i18n key
  • The partial renders one <ol> of <li> entries; the current page is a <span> with aria-current="page", page links carry an aria-label (“Go to page N”), and the ‹ › icon links are named by the previous and next i18n keys
  • Disabled arrows and ellipses render as <span aria-hidden="true">, so screen readers hear only real destinations
  • Hugo pagination is plain links — Tab then Enter; the client-side control uses native buttons that initTableLists disables at either end
  • The client-side page status carries aria-live="polite" and aria-atomic="true", so screen readers hear the new page position when Previous or Next changes the visible rows
  • Avoid conveying the current page by highlight alone — aria-current="page" is the signal non-visual readers get; keep it when reusing the classes by hand

Hugo pagination:

  • Blog index: footer pagination, 12 rows per page
  • TIL index: footer pagination, 12 rows per page
  • Links index: compact top pagination plus footer pagination, 12 rows per page
  • Books index: footer pagination, 12 rows per page

Client-side table pagination:

  • Publications table: table filters, sorting, and row paging. This should remain client-side as the list grows because readers are filtering and sorting one dataset, not navigating a content archive.

Watchlist#

These pages may need pagination or stronger filtering as they grow:

  • Publications: expected to reach roughly 60 rows soon; keep client-side table pagination
  • Blog series index: may eventually need table pagination or Hugo pagination if the number of series grows
  • Individual series index pages: may need table pagination if a series becomes long
  • Quotes: large reference dataset; needs filtering or pagination if the browsing experience becomes heavy
  • Thank-you: medium-sized reference dataset; revisit if it keeps growing