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.
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.
| Part | Rule |
|---|---|
| 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. |
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.
- The current page belongs in the URL, so refresh, back and a bookmark all behave. This is basic and frequently missing, and its absence is felt hardest by the reader who fat-fingers the browser back button after opening a result.
- A link to an item addresses the item, never its page. Off-page alarm routes, results in a message, anything a clinician sends to a colleague — these resolve to the record, and the interface finds whichever page it now falls on.
- Returning from an item restores the page and the scroll position, because the alternative is re-reading a list to find your place, which is where the second-to-last row gets skipped.
- If the set changed while you were away, say so rather than silently renumbering — the same objection as Reprioritisation.
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
| State | Rendering |
|---|---|
| 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.
| Rule | Held by | How |
|---|---|---|
| The range and the total are always stated | The 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 named | The 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 ends | The 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 why | The 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 so | The 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 total | You | 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 pages | You | 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 3 | You | The component holds no selection and does not know one exists. |
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
Range, total and order. The clinician knows exactly how much of the record is off-screen and why it is off-screen.
A page count instead of a set size. Three pages of what — thirty results or three hundred?
A page boundary is a display convenience, not a boundary on what the clinician is told.
Two urgent results sit on page 3 and nothing on page 1 says so. Most clinicians never reach page 2.
One page, controls hidden, range line still present. "You are seeing everything" is worth saying out loud.
A capped count. The difference between 100 and 900 is the difference between a manageable list and an unworkable one.
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.
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
- Pagination is a
<nav>with anaria-label, and each control is labelled by page — "Page 3", not "3". aria-current="page"on the active control, alongside fill and weight.- The range line is a live region so changing page announces the new range and total.
- Focus after a page change moves to the top of the set's region, not to the next pagination button, so a keyboard user is not left below the content they requested.
- Targets ≥ 24 px with ≥ 24 px between centres (WCAG 2.2 SC 2.5.8). Page numbers are the control most often built at 18 px.
- Disabled previous/next stay in the tab order as
aria-disabledwith a reason, rather than vanishing and shifting the row under a travelling pointer. - Reflow to 320 px — page numbers collapse to previous/next plus the range line, never to an icon-only pair (SC 1.4.10).
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.
- Effectiveness — the clinician knows the true size of the set and is told about anything urgent they cannot currently see.
- Efficiency — a count answers "is it worth opening?" without opening anything, which is most of what counts are for.
- Satisfaction — exact numbers. A capped or approximate count is the kind of small dishonesty that makes people stop believing the rest of the screen.
Clinical safety notes
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.
- Range and total always shown. (the component) Mitigates: a page read as the whole record.
- Order stated alongside the range. (you) Mitigates: items moving between pages under an order the clinician cannot see.
- Off-page urgent items reported above the set. (you) Mitigates: an unacknowledged result on a page never visited.
- Alarm queues, acknowledgement lists and handover views are never paginated. (you) Mitigates: a page boundary read as the end of the work.
- Position re-anchored when the set changes. (you) Mitigates: acting on a row that moved beneath the page index.
- Previous page retained while loading. (you) Mitigates: a blank region read as an empty set.
- Links address items, never page numbers. (you) Mitigates: a shared reference resolving to a different set once the list has moved on.
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" />
Related
- Badge — the count that says how large the set is, and what narrows it.
- Filter — the same "what am I not seeing?" rule, applied to criteria.
- Data grid — virtual scrolling, the preferred alternative to pagination.
- Pending & unreviewed work — why an acknowledgement list is never paginated.
- Reprioritisation — a set changing under a reader who has not moved.
- Typography — tabular numerals in page numbers and ranges.