Skip to content
Home Theme Gallery

Pagination

The custom element is <hui-pagination>.

Overview

Renders real <a href> links, so a host drives navigation with hx-get and the control still works without JavaScript. It owns only the page window, never the rows or counts.

Example

Page window
Source
<hui-pagination page="3" total="10" sibling-count="1" href="/items?page={page}" label="Pages"></hui-pagination>

The Go template that renders it:

<hui-pagination page="{{ .Page }}" total="{{ .Total }}" sibling-count="{{ .Siblings }}" href="{{ .Href }}" label="{{ .Label }}"></hui-pagination>

On a phone

When the full row does not fit, pagination steps down one form at a time until it does: Previous and Next become arrows (still named for assistive technology), then the window narrows to the current page between the first and last, then the numbers give way to “Page 6 of 20”. The form is measured, never written by the host, and reflected as data-compact.

Twenty pages, then two narrower containers 375 px wide
Source
<div style="display:grid;gap:1rem;justify-items:start">
<hui-pagination page="6" total="20" sibling-count="2" href="/items?page={page}"></hui-pagination>
<div style="width:240px"><hui-pagination page="6" total="20" href="/items?page={page}"></hui-pagination></div>
<div style="width:180px"><hui-pagination page="6" total="20" href="/items?page={page}"></hui-pagination></div>
</div>

API

Attributes6
Attributes
NameTypeDefaultDescription
pagenumber1The current page, clamped between 1 and total. Reflects to the page attribute.
totalnumber1The number of pages. Reflects to the total attribute.
sibling-countnumber1How many pages to show either side of the current one before collapsing to an ellipsis.
hrefstring""Link template; {page} is replaced with each page number. Empty falls back to #page-N.
labelstring"Pagination"Accessible name for the <nav>.
data-compact0 | 1 | 2 | 30Set by the element, never the host: which narrower form it is showing to fit its container. 1 draws Previous and Next as arrows, 2 also drops the siblings, 3 shows “Page N of M” in place of the numbers.
Properties5
Properties
NameTypeDefaultDescription
pagenumber1Reflects to the page attribute.
totalnumber1Reflects to the total attribute.
sibling-countnumber1Reflects to the sibling-count attribute.
hrefstring''Reflects to the href attribute.
labelstring'Pagination'Reflects to the label attribute.
CSS custom properties5
CSS custom properties
NameDefaultDescription
--hui-accentoklch(0.96 0.003 325.6)Background of a hovered link.
--hui-inputoklch(0.62 0.019 323.02)Border of the current-page link.
--hui-muted-foregroundoklch(0.542 0.034 322.5)Colour of the ellipsis.
--hui-radius-smcalc(var(--hui-radius, 0.45rem) * 0.6)Corner radius of a link.
--hui-text-sm0.875remFont size of the links.
::part() hooks1
::part() hooks
NameDescription
ellipsisThe aria-hidden "…" that stands in for a collapsed run of pages.

States

Single page

A single page: the numbers collapse to one link, and Previous and Next are both aria-disabled.

Single page
Source
<hui-pagination page="1" total="1" href="/items?page={page}"></hui-pagination>

Wider window

sibling-count="2" shows two pages either side of the current one. When that does not fit its container the element steps down to a narrower form, which the phone example above shows.

Wider window
Source
<hui-pagination page="6" total="20" sibling-count="2" href="/items?page={page}"></hui-pagination>

On the first page

On the first page, Previous is aria-disabled and the window starts at page 1.

On the first page
Source
<hui-pagination page="1" total="10" sibling-count="1" href="/items?page={page}"></hui-pagination>

Server-side mechanics

The server owns the rows and the total count; it renders <hui-pagination> with only page, total, an href template and a label. The href is expanded with {page}, so real anchors point at server URLs and the control works without JavaScript, while the component owns only which page numbers appear. It is not form-associated, so it has no name and submits nothing. An hx-swap that replaces the pagination after a page change is the normal path; replacing it mid-click is harmless because each link is an ordinary navigation.

Accessibility

  • The control is a <nav> named by label; the current page carries aria-current="page".
  • Every link has an accessible name: "Previous page", "Page N" or "Next page".
  • Previous and Next carry aria-disabled at the ends, but remain links so the no-JavaScript path is unchanged.

Keyboard

Keyboard
KeysAction
TabMoves focus through the real links in order.
EnterFollows the focused link, exactly as a normal anchor does.

Gotchas

  • Previous and Next carry aria-disabled="true" at the ends but remain real links, so they are still focusable and followable; the host must guard the action itself.
  • With an empty href the links fall back to #page-N anchors, which navigate nowhere.