LayoutReact
The controls of a paged list: a navigation landmark named "Pagination" with First, Previous, the page range, Next and Last.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<Pagination.Root>
<Pagination.Previous />
<Pagination.Collection>
<Pagination.Item />
<Pagination.Ellipsis />
</Pagination.Collection>
<Pagination.Next />
</Pagination.Root>
// Not in this example: Pagination.First, Pagination.Last, Pagination.PageSize, Pagination.Status, Pagination.ValueExamples
Buttons
Button mode with First and Last on 20 pages: each press reports its reason, and at a bound the control stays focusable with aria-disabled.
Compact
The data table shape: Value reads "Page 1 of 5" between four buttons, under a table of 23 invoices, 5 per page, from itemCount and pageSize.
Page Size
itemCount of 95 with PageSize of 10, 20 or 50: a new size keeps the first row of the page, Value shows the item range and Status announces it.
Roving
focusMode="roving": one Tab stop for the controls; the arrow keys, Home and End move between them, and the text next to them says so.
Unknown Total
No total, as with a cursor: hasNextPage is true until the fourth page, Value reads "Page 1" without "of", and there is no Last.
API reference
31 props across 11 parts, in React syntax. className, style, render are listed once, under Shared props.
CollectionPagination.Collection
Calls its function child once per entry of the page range: a page, or an ellipsis. Entries are keyed by page, so a pressed item keeps its element and focus when the range moves. Doesn’t render its own element.
| Prop | Type | Default |
|---|---|---|
childrenrequiredRenders one entry: { type: 'page', value, key } or | (entry: PaginationEntry, index: number) => React.ReactNode | — |
EllipsisPagination.Ellipsis
Stands for the pages the range hides, … unless it has children. It is hidden from assistive technology. Renders a <span>.
| Data attribute | Description |
|---|---|
data-position | Whether the hidden pages are before or after the current page. |
FirstPagination.First
Goes to the first page. It is aria-disabled, not disabled, on the first page, so it keeps focus., or an <a> element when the root has getPageHref. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
ItemPagination.Item
Goes to one page. The item of the current page has aria-current="page" and keeps its href in link mode., or an <a> element when the root has getPageHref. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
valuerequiredThe page the item goes to, 1-based. | number | — |
| Data attribute | Description |
|---|---|
data-active | Present on the item of the current page. |
data-disabled | Present when the pagination is disabled. |
LastPagination.Last
Goes to the last page. It is aria-disabled, not disabled, on the last page, so it keeps focus. Renders nothing while the number of pages is unknown., or an <a> element when the root has getPageHref. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
NextPagination.Next
Goes to the next page. It is aria-disabled, not disabled, on the last page, so it keeps focus., or an <a> element when the root has getPageHref. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
PageSizePagination.PageSize
Chooses the number of items per page. A change keeps the first item of the current page visible: the page moves with reason page-size-change. with one <option> per size, unless it has children. Renders a <select>.
| Prop | Type | Default |
|---|---|---|
optionsThe sizes to choose from. | readonly number[] | — |
PreviousPagination.Previous
Goes to the previous page. It is aria-disabled, not disabled, on the first page, so it keeps focus., or an <a> element when the root has getPageHref. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
RootPagination.Root
Groups all parts of the pagination: a navigation landmark of the controls of a paged list. Pages are 1-based. Renders a <nav>.
| Prop | Type | Default |
|---|---|---|
valueThe current page, 1-based. | number | — |
defaultValueThe page when the pagination is initially rendered. | number | 1 |
onValueChangeEvent handler called when the page changes. | ((value: number, eventDetails: PaginationRootChangeEventDetails) => void) | — |
totalPagesThe number of pages. | number | — |
itemCountThe number of items. | number | — |
pageSizeThe number of items per page. | number | — |
defaultPageSizeThe number of items per page when the pagination is initially rendered. | number | 10 |
onPageSizeChangeEvent handler called when the page size changes from Pagination.PageSize. | ((pageSize: number, eventDetails: { reason: "page-size-change"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element | undefined; }) => void) | — |
siblingCountThe number of pages shown on each side of the current page. | number | 1 |
boundaryCountThe number of pages always shown at each end. | number | 1 |
fixedItemsWhether the range keeps the same number of entries on every page, so the controls do not | boolean | true |
hasNextPageWhether a next page exists while the number of pages is unknown. | boolean | true |
getPageHrefBuilds the href of a page. | ((page: number) => string) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
focusModeHow focus moves between the controls: 'tab' makes each a Tab stop; 'roving' gives one | PaginationFocusMode | 'tab' |
orientationThe orientation the arrow keys follow in focusMode="roving". | Orientation | 'horizontal' |
loopFocusWhether the arrow keys wrap from the last control to the first in focusMode="roving". | boolean | true |
labelsThe texts, in English by default. | Partial<PaginationLabels> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the pagination is disabled. |
StatusPagination.Status
A polite live region that announces the new page, or the new item range when the root has itemCount, after each page or page-size change. It is empty on the first render and visually hidden. Renders a <div>.
ValuePagination.Value
The current page as text: "Page 3 of 10", or "Page 3" while the number of pages is unknown. Plain text, not a live region. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
childrenThe text, or a function of the page, the number of pages, the page size and the item range. | React.ReactNode | ((values: PaginationValueValues) => React.ReactNode) | — |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| Tab | Moves to the next control: by default every control is a Tab stop | Item |
| EnterSpace | On a button: opens its page and keeps focus on it while the range moves; on a link, Enter follows it | Item |
| EnterSpace | Nothing: Previous on the first page and Next on the last stay focusable and do not change the page | Previous |
| ControlMetaShiftAlt | On a link: opens it in a new tab or window as the browser does, and the page does not change | Item |
| TabfocusMode="roving" | Enters and leaves the controls as one Tab stop, on the current page | Root |
| ArrowLeftArrowRight | With focusMode="roving": moves focus between the controls, skipping the ellipsis; the page stays | Root |
| HomeEnd | With focusMode="roving": moves focus to the first or the last control | Root |
| ArrowUpArrowDown | With focusMode="roving" and orientation="vertical": move focus instead of the left and right arrows | Root |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | a nav named "Pagination" (labels.root, or your aria-label or aria-labelledby) | the controls are a landmark a screen reader lists and jumps to; name each one when a page has two |
| Item | aria-current="page" on the current page only, aria-label "Page 4"; in link mode the current page keeps its href | the current page is read as "current page, Page 4", and every page has a name beyond its number |
| Previous | at the first page aria-disabled="true", focusable; in link mode no href and no rel, with role="link" | focus stays on the control when it reaches its bound, and a disabled link is announced as dimmed, not lost |
| Next | rel="next" on an enabled link, rel="prev" on Previous | the browser and crawlers know the order of the pages |
| Ellipsis | aria-hidden="true", … unless it has children | a screen reader skips it; the pages around it already say which numbers are missing |
| Status | role="status", aria-live="polite", aria-atomic, visually hidden; empty on the first render | "Page 5 of 10" or "Showing 41–60 of 95" is read after a change, without moving focus (WCAG 4.1.3) |
| PageSize | a native select named "Items per page" (labels.pageSize) | the platform's own picker opens on touch and with the keyboard, and the arrow keys stay the select's |
Focus order
- Each control is a Tab stop in DOM order: First, Previous, the pages, Next, Last. The ellipsis is never focused. Nothing takes focus on mount.
- After a press in button mode, focus stays on the pressed control: the pages are keyed by number, so the element of page 7 stays when the range moves, and a control at its bound stays focusable.
- With
focusMode="roving"the controls are one Tab stop, the current page first; the arrow keys, Home and End move between them and wrap unlessloopFocusisfalse.PageSizestays a Tab stop of its own. - In link mode with a full page load, focus is the browser's on the new page.