Components · Data

Pagination

A page is a claim about a set: these are the ones you are looking at, and there are this many more. It is the only component here whose job is to hide things, so everything it hides has to be accounted for on screen.

Stable · v1.0 shadcn/ui WCAG 2.2 AA SC 2.5.8 · 1.4.10

Overview

Pagination is the more consequential half of the pair it used to share a page with, because it is the one that hides things. Its rule follows directly from Filter: a clinician must always be able to see the size of the whole set, and must be told when something outside the current page needs attention.

The count on a tab or a row beside it is a Badge, and the two answer the same question from opposite ends — how many there are, and which of them you can see.

When to paginate, and what a page must carry

Prefer not to paginate at all. Virtual scrolling with a fixed header keeps the set continuous and the total visible, and it does not create the class of error where a patient exists on page three. Paginate when the set is genuinely large, the order is stable, and a page boundary carries no clinical meaning.

Position, total, and what is elsewhere
2 unacknowledged urgent results on page 3
PartRule
Range and total Required. 1–25 of 63. Never "Page 1 of 3" alone — the clinician needs the set size, not the page count.
Order statement Required. Page boundaries are meaningless without a stated order, and a changing order moves items between pages.
Current page aria-current="page" plus fill and weight. Never fill alone.
Off-page alarms Anything unacknowledged at advisory priority or above on another page is reported above the set, with a route to it.
Page size Fixed per view and remembered per session. It never changes as a side effect of sorting or filtering.
Where pagination is prohibited

Never paginate an alarm queue, an acknowledgement list, a handover view, or any set a clinician is expected to work to zero. In those views every item must be reachable by scrolling, because a page boundary is an invitation to stop. See Triage worklist and Acknowledge & escalate.

Pages are not addresses

Pagination has a property that is easy to miss and awkward once noticed: a page number is not a stable reference to anything. "Page 3" of a live worklist contains different patients tomorrow, and a colleague opening the link you sent sees a different set.

Why not infinite scroll

It is the common alternative and it is rejected here. It removes the one thing this component exists to provide — knowing whether you have seen everything. There is no total, no position, no end state distinguishable from a slow fetch, and no way to return to where you were. It also strands anything below the list, and on a screen reader it produces a set whose length is never announced.

Virtual scrolling is different and is preferred: the set is continuous, the scrollbar reports its size, and the total stays on screen. What is rejected is loading more as a substitute for knowing how much there is.

States

StateRendering
Single page Controls hidden; the range line still renders — 1–19 of 19.
Loading a page The previous page stays on screen with activity shown. Pagination never blanks the region, and the control keeps its width so the layout does not jump.
Total unknown 1–25 of at least 25 with the reason. Never a fabricated total and never a bare page number.
Set changed underneath Position is re-anchored to the item the clinician was on, and the change is stated. Never silently the same page index over different content.

What the component enforces, and what it only asks for

Small surface, clean split: the component states the position and the size of the set, and nothing else about the set is knowable from inside it.

RuleHeld by How
The range and the total are always statedThe component page, pageSize and total are all required, so “Page 3” with no denominator is not expressible.
Asserted: Pagination: the denominator is always present
The items are namedThe component itemLabel is required — “21–40 of 340 studies”, not “21–40 of 340”.
Asserted: Pagination: the items are named, not just counted
Neither control pages past the endsThe component Previous is disabled on page 1 and Next on the last page, so onPageChange is never asked for a page that does not exist. Driven in a browser, not only rendered.
Asserted: Pagination: next is disabled on the last page
A control at the end stays in the tab order and says whyThe component At the first or last page the control is aria-disabled with “This is the first page” / “This is the last page” as its description, read from the accessibility tree. It is not removed from the keyboard and the row does not shift. Pressing it does nothing.
Asserted: AX tree: a disabled pager control is disabled to the tree, and the nav is named
A page that no longer exists says soThe component When the set shrinks under the reader, the summary says the page no longer exists and how many remain. It printed “Showing 41–5 of 5”.
Asserted: Pagination: a page past the end says so rather than printing an impossible range
The total is the real totalYou If a filter is hiding rows, total is the filtered total and the control will say so with complete confidence.
The order is stable between pagesYou Paging through a set that is being re-sorted or appended to shows some records twice and others never. Nothing here can detect it.
A selection made on page 1 still exists on page 3You The component holds no selection and does not know one exists.
Why this is written down

A rule stated on a component page reads as a rule the component keeps. 2 of these 5 are; 3 are obligations this page was quietly handing to the reader. An unstable order under a paginated view is how a record is never seen at all, and it is invisible to this component.

Do's and don'ts

Do
1–25 of 63 · ordered newest first

Range, total and order. The clinician knows exactly how much of the record is off-screen and why it is off-screen.

Don't
Page 1 of 3

A page count instead of a set size. Three pages of what — thirty results or three hundred?

Do
2 unacknowledged urgent results on page 3

A page boundary is a display convenience, not a boundary on what the clinician is told.

Don't

Two urgent results sit on page 3 and nothing on page 1 says so. Most clinicians never reach page 2.

Do
1–19 of 19 · ordered newest first

One page, controls hidden, range line still present. "You are seeing everything" is worth saying out loud.

Don't
Showing 99+ results

A capped count. The difference between 100 and 900 is the difference between a manageable list and an unworkable one.

Do

Sent to a colleague:
…/results/4417-2295/troponin-14261

Addresses the result. Whichever page it falls on when they open it, it is the thing you meant.

Don't

Sent to a colleague:
…/worklist?page=3

Page 3 of a live worklist holds different patients by the time they open it. The link resolves, looks right, and shows the wrong set.

Accessibility

Outcomes of use

What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.

Clinical safety notes

Risk controls this page specifies

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/pagination.json
// The total is required and exact. There is no unknown-total mode:
// a worklist whose length the software will not state is one a clinician
// cannot finish, because they cannot tell whether they are near the end.
<Pagination page={2} pageSize={25} total={340} itemLabel="studies" />
NotJustAnyMed.Tech Design System · Pagination · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data and reference ranges shown are fabricated and illustrative.