Pagination
Moves between pages of results, with previous and next controls and a compact page range.
import { Pagination } from "@merid/react";
export function Example() {
return <Pagination pageCount={12} defaultPage={5} />;
}Import
import { Pagination, getPageRange } from "@merid/react";Anatomy
Pagination is a single component. It renders:
- a
<nav>landmark with a list inside, - a previous control and a next control (icon buttons with labels),
- page buttons — the first, the last,
siblingCountpages each side of the current one, - an ellipsis wherever two or more pages are skipped.
Examples
Controlled
Pass page and onPageChange when the page lives in your state or the URL.
Showing results 1–20
import { useState } from "react";
import { Pagination } from "@merid/react";
export function Example() {
const [page, setPage] = useState(1);
return (
<div>
<p>Showing results {(page - 1) * 20 + 1}–{page * 20}</p>
<Pagination pageCount={8} page={page} onPageChange={setPage} />
</div>
);
}More siblings
siblingCount sets how many pages show on each side of the current one.
<Pagination pageCount={40} defaultPage={20} siblingCount={2} />As links
With getHref, each other page renders as an <a> so results are crawlable and open in a new tab. The current page renders as plain text (not a link to itself) with aria-current="page". Disabled previous/next controls stay buttons.
<Pagination
pageCount={6}
defaultPage={2}
getHref={(page) => `?page=${page}`}
aria-label="Search results pages"
/>For client-side routing, add renderLink. It receives every prop of the link (href, className, aria-label, aria-current, onClick, children, …) plus the target page:
import NextLink from "next/link";
<Pagination
pageCount={12}
page={page}
getHref={(p) => `/invoices?page=${p}`}
renderLink={({ page, ...props }) => <NextLink {...props} />}
/>Custom layouts with getPageRange
getPageRange(page, pageCount, siblings = 1) returns the same range the component uses, as an array of page numbers and "ellipsis-start" / "ellipsis-end" markers.
getPageRange(5, 12); // [1, "ellipsis-start", 4, 5, 6, "ellipsis-end", 12]API reference
Pagination
Renders <nav> and accepts its HTML attributes except onChange.
| Prop | Type | Default | Description |
|---|---|---|---|
pageCountRequired | number | – | Total number of pages (≥ 1). |
page | number | – | Controlled current page, 1-based. |
defaultPage | number | 1 | Initial page when uncontrolled. |
onPageChange | (page: number) => void | – | Called with the requested page. |
siblingCount | number | 1 | Pages shown on each side of the current one. |
getHref | (page: number) => string | – | When given, pages render as links with this href; the current page renders as text. |
renderLink | (props: PaginationLinkProps) => ReactNode | – | With getHref, renders each link yourself (e.g. a router link). Spread every prop except `page` onto an element that renders an `<a>`. |
aria-label | string | "Pagination" | Accessible name of the landmark. |
previousLabel | string | "Previous page" | Label of the previous button. |
nextLabel | string | "Next page" | Label of the next button. |
pageLabel | (page: number) => string | (p) => `Page ${p}` | Builds the accessible label for a page button. |
ref | Ref<HTMLElement> | – | Forwarded ref to the nav element. |
getPageRange
| Prop | Type | Default | Description |
|---|---|---|---|
pageRequired | number | – | Current page, clamped to 1…pageCount. |
pageCountRequired | number | – | Total pages. |
siblings | number | 1 | Pages on each side of the current one. |
Returns PageRangeItem[], where PageRangeItem = number | "ellipsis-start" | "ellipsis-end".
Styling
.mrd-pagination/.mrd-pagination__list— nav root and the flex list..mrd-pagination__page— a page button, link, or (current page in link mode) text:--mrd-icon-button-mdsquare,--mrd-radius-md..mrd-pagination__page[aria-current="page"](alsodata-state="active") — current page:--mrd-accent-softfill,--mrd-accent-strongtext..mrd-pagination__controlwithdata-direction="previous"or"next"— chevron controls;:disableduses--mrd-placeholder..mrd-pagination__ellipsis— the…gap, hidden from assistive tech.
On coarse pointers controls grow to --mrd-icon-button-lg. Reduced motion removes the press scale.
Accessibility
There is no dedicated APG pattern for pagination; it follows the landmark regions guidance for a named navigation region.
- The list sits inside a
navnamed "Pagination". Name it more specifically (for example "Search results pages") when a page has several. - Every page control has an accessible label from
pageLabel; the current page hasaria-current="page". - Previous and next are disabled, not hidden, at the ends, so the layout does not shift.
- Ellipses are
aria-hidden.
| Key | Action |
|---|---|
| Tab | Moves focus to the next control. |
| Enter | Activates the focused page or control. |
| Space | Activates the focused button (button mode). |
Guidelines
Do
Avoid
Do
Avoid