Pagination#

A page picker over a numbered range. Unlike the compound components, zero renders the row itself: the visible window derives from count, the page model and the windowing props, so a consumer could not compose what it cannot compute. The result is a <nav> landmark of ordinary buttons — the current page marked aria-current="page", the ends bracketed by prev / next triggers that disable at the bounds.

Import#

TSX
import { Pagination, paginationRow } from '@sigx/zero/pagination';

Pagination is a compound with a single member, Pagination.Root. It is also re-exported from the @sigx/zero root, together with paginationAnatomy and the paginationRow helper.

Usage#

TSX
import { component } from 'sigx';
import { Pagination } from '@sigx/zero/pagination';

const Results = component(({ signal }) => {
    const state = signal({ page: 1 });

    return () => (
        <>
            <ResultList page={state.page} />
            <Pagination.Root count={12} model={() => state.page} />
        </>
    );
});

model={() => state.page} binds the current page both ways — clicking a page writes state.page, and writing state.page moves the row. Leave the model off and pass defaultPage to keep the state inside the component; pageChange fires either way. See Models.

Every number the consumer passes is sanitised: count is at least 1, the page is clamped into 1..count, and fractional values are floored, so the row never renders a page that does not exist.

The window#

TSX
<Pagination.Root count={40} siblingCount={2} boundaryCount={1} model={() => state.page} />

The row is a constant-width window: boundaryCount pages pinned at each end, siblingCount pages on each side of the current one, and an ellipsis wherever the window elides. When the current page sits near an edge the sibling block slides instead of shrinking, so page 1 of many shows as wide a row as page 5 and the triggers never move as the user walks.

Labels#

TSX
<Pagination.Root
    count={12}
    label="Search results pages"
    prevLabel="Previous results"
    nextLabel="Next results"
    model={() => state.page}
/>

label names the <nav> landmark (default "Pagination"); prevLabel and nextLabel name the triggers (default "Previous page" / "Next page"). The triggers render the and glyphs as content, but their accessible name always comes from aria-label, never from the glyph. Each item button is named "Page n".

Computing the row yourself#

TSX
import { paginationRow } from '@sigx/zero/pagination';

paginationRow(5, 12, 1, 1);
// → [1, 'start-ellipsis', 4, 5, 6, 'end-ellipsis', 12]

paginationRow(page, count, siblingCount, boundaryCount) is the pure windowing function the component renders from. It returns an array of page numbers and the two ellipsis markers, 'start-ellipsis' and 'end-ellipsis' — useful for a server-rendered list of links or a test that asserts the window shape.

Anatomy#

PartElementStatesFlagsNotes
rootnavdisabledaria-label from label. Carries the variant axes.
itembuttonactive | inactivedisabled, focus-visible, pressed, press-animatingOne per visible page; aria-current="page" when active, aria-label="Page n". Publishes press feedback.
ellipsisspanaria-hidden where the window elides. Inside root.
prev-triggerbuttondisabled, focus-visible, pressed, press-animatingSteps back one page; disabled on page 1. Carries the glyph.
next-triggerbuttondisabled, focus-visible, pressed, press-animatingSteps forward one page; disabled on the last page. Carries the glyph.

Every part carries data-scope="pagination" and data-part="<part>". The current page is the activation state — the same active / inactive pair as breadcrumbs' current link and tabs' selected tab. There is no list: the windowed row is a strip of controls, not content, so wrapper <li> elements would be elements no part could honestly claim. See The anatomy contract.

Props#

Pagination.Root#

PropTypeDefaultDescription
modelnumberTwo-way binding of the current page (1-based).
defaultPagenumber1Initial page when uncontrolled.
pageChangeevent (page: number)Fires whenever the page changes.
countnumberrequiredTotal number of pages.
siblingCountnumber1Pages shown on each side of the current page.
boundaryCountnumber1Pages pinned at each end of the row.
labelstring'Pagination'Accessible name of the navigation landmark.
prevLabelstring'Previous page'Accessible name of the previous-page trigger.
nextLabelstring'Next page'Accessible name of the next-page trigger.
disabledbooleanfalseDisables every button; renders data-disabled on the root and each button.
color / size / variant / axes / modsdesign-system vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

Keyboard#

KeyAction
Tab / Shift+TabMove between the triggers and every page button — each is its own tab stop.
Enter / SpaceActivate the focused button: select that page, or step with a trigger.

There is deliberately no roving tabindex. Roving is for composites where one widget owns many stops; here each page is a distinct, meaningful destination exactly like any other button row, and collapsing them to one stop would hide the row from a keyboard user walking by Tab. There is no APG pagination pattern to defer to, so the semantics stay at the honest minimum: a landmark, aria-current on the current page, and ordinary buttons.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (the eight recommended roles) and size (xsxl) on the pagination root, so <Pagination.Root color="primary" size="sm"> is styled in both. Neither wires a variant or any mods on the scope; under a design system's /register import those props are therefore absent. See Typed vocabulary.

The trigger glyphs are physical ink: points left whatever the reading direction. A recipe corrects that under RTL with transform: scaleX(-1) on the two trigger parts inside its rtl guard — the same treatment as every other pointing chevron in the shipped skins.

Breadcrumbs for the other navigation landmark, Table for the data the pages usually page through.