Pagination
URL-driven page controls that sync the current page with the page query parameter in the Next.js App Router.
Pagination is a client component that renders previous/next buttons and a compact list of page numbers, for paged lists and search results. It reads and writes the page search parameter through next/navigation, so it requires the Next.js App Router. Page changes in the demos update ?page= in the address bar.
Import#
import { Pagination, PaginationList } from "@reactberry/system/blocks";
Usage#
"use client";
import { Suspense } from "react";
import { Pagination } from "@reactberry/system/blocks";
import { Box, Text } from "@reactberry/system/elements";
export default function ResultsFooter({ total }: { total: number }) {
return (
<Box display="flex" flexDirection="column" gap="s">
<Text color="secondary">{total} results</Text>
<Suspense fallback={null}>
<Pagination
total={total}
pageSize={20}
onPageChange={(page) => console.log("Page", page)}
/>
</Suspense>
</Box>
);
}
Examples#
Page size#
pageSize sets the number of items per page. Nothing is rendered when total fits on one page.
PaginationList#
PaginationList renders children followed by the controls in a sticky footer. It does not slice children; render only the items for the current page.
API#
Pagination#
| Prop | Type | Default | Description |
|---|---|---|---|
total* | number | — | Total number of items across all pages. |
pageSize | number | 10 | Items per page; used to compute the page count. |
onPageChange* | (page: number) => void | — | Called with the 1-based target page before the router navigates. |
No other props are accepted and nothing is spread.
PaginationList#
| Prop | Type | Default | Description |
|---|---|---|---|
total* | number | — | Passed to |
pageSize | number | 10 | Passed to |
children* | ReactNode | — | The list content rendered above the controls. |
onPageChange | (page: number) => void | — | Called with the 1-based page when it changes. |
The controls are wrapped in a Box with position="sticky", bottom="0", bg="base" and p="small". No other props are accepted. The source comment mentions a default page size of 20, but the code default is 10.
Accessibility#
- The controls are wrapped in a
navelement without anaria-label. - The current page button has
aria-current="true"and no click handler. Other page buttons havearia-labelvalues such as"Page 3". - The previous and next buttons switch their
aria-labelto"No previous page available"/"No next page available"on the first and last page. - Buttons are
Buttonelements rendered asspanwithrole="button"andtabIndex={0}. They can be focused but have no Enter or Space handling, anddisabledonly blocks pointer events.
Notes#
- The current page is read from
searchParams.get("page")and defaults to1. Page numbers are 1-based. EveryPaginationon a route reads the same parameter, so the examples on this page share it. - Changing page calls
onPageChangeand thenrouter.pushwith the current pathname and the updatedpageparameter; other query parameters are preserved. - Up to three page numbers are shown around the current page, with the first and last pages and
…placeholders added as needed. Placeholder buttons are disabled. - The previous and next buttons are disabled on the first and last page.
- Because it uses
useSearchParams, wrap the component in aSuspenseboundary in statically rendered routes.