Pagination
A hand-drawn pager for splitting long lists across numbered pages.
Overview
Pagination splits a long list of results across numbered pages, with Previous/Next controls and an
ellipsis that collapses large ranges. It is controlled — you own the current page and update it
from onPageChange.
Anatomy
A <nav> landmark wrapping Previous, a run of numbered page buttons, and Next. Each button is a
sketchy box (@ghds/sketch-core); the current page is filled and marked aria-current="page".
Large gaps collapse to a single ellipsis, keeping the first, last, and the pages around the current
one (siblingCount).
Variants & States
| State | Status | Notes |
|---|---|---|
| Current page | Implemented | Filled + aria-current="page". |
| Disabled | Implemented | Previous on the first page and Next on the last. |
| Ellipsis | Implemented | Collapses a run of hidden pages; a single hidden page is shown instead. |
| Hover / Focus | Implemented | Standard button focus ring; keyboard operable. |
Usage
Do
- Keep it controlled: store the page and update it in onPageChange.
- Tune siblingCount for how many neighbours to show around the current page.
- Pair it with a results summary ("Showing 21–40 of 200") for context.
Don't
- Don't use pagination for continuous feeds — infinite scroll or a Load more button fits better.
- Don't hide Previous/Next; disable them at the ends instead.
Accessibility
- React / Web Components: a
<nav aria-label="Pagination">landmark; each control is a real<button>with a descriptivearia-label(“Page 3”, “Previous page”); the current page carriesaria-current="page". Web Components dispatches apage-changeevent. - React Native: the row exposes
role="navigation"; each control isaccessibilityRole="button"with a label andaccessibilityState(selectedfor the current page,disabledfor the ends). - Every control is keyboard-operable on the web platforms.
| Key | Action |
|---|---|
Tab | Move between the pagination controls. |
Enter / Space | Activate the focused control. |
Content
Numeric page labels, plus “Previous”/“Next” accessible labels. No visible prose beyond the numbers and chevron icons.
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Total number of pages. |
page | number | — | Current page (1-based). |
onPageChange | (page: number) => void | — | Fires with the next page. |
siblingCount | number | 1 | Pages shown on each side of the current one. |
label | string | 'Pagination' | Accessible name for the nav landmark. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Total number of pages. |
page | number | — | Current page (1-based). |
siblingCount | number | 1 | Pages shown on each side. |
label | string | 'Pagination' | Accessible name. |
page-change event | CustomEvent<number> | — | Dispatched with the next page. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Total number of pages. |
page | number | — | Current page (1-based). |
onPageChange | (page: number) => void | — | Fires with the next page. |
siblingCount | number | 1 | Pages shown on each side. |
label | string | 'Pagination' | Accessible name. |
testID | string | — | Test handle for queries. |
Code examples
// React — @ghds/react
import { Pagination } from '@ghds/react/pagination';
import { useState } from 'react';
const [page, setPage] = useState(3);
<Pagination count={12} page={page} onPageChange={setPage} />;
<!-- Web Components — @ghds/web-components -->
<script type="module">
import '@ghds/web-components/pagination';
</script>
<gh-pagination id="pager" count="12" page="3"></gh-pagination>
<script>
const el = document.getElementById('pager');
el.addEventListener('page-change', (e) => {
el.page = e.detail;
});
</script>
// React Native — @ghds/react-native
import { Pagination } from '@ghds/react-native/pagination';
import { useState } from 'react';
const [page, setPage] = useState(1);
<Pagination count={12} page={page} onPageChange={setPage} />;