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:
| Option | Purpose |
|---|---|
paginator | The Hugo paginator for the list (required) |
variant | footer (default) with range summary and totals, or compact with page controls only |
data-page/table-pagination.html takes two optional overrides instead:
| Option | Purpose |
|---|---|
class | Wrapper <nav> classes; defaults to the publications page’s own layout |
ariaLabel | Landmark 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 thepaginationi18n key - The partial renders one
<ol>of<li>entries; the current page is a<span>witharia-current="page", page links carry anaria-label(“Go to page N”), and the ‹ › icon links are named by thepreviousandnexti18n 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
initTableListsdisables at either end - The client-side page status carries
aria-live="polite"andaria-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