# Pagination

**When to use:** Move through server-paged results a page at a time — a results list, an admin table, a report. For endless scrolling instead, or for a long list that's all in memory, use `VirtualList`.

**Keywords:** pager, paging, page numbers, next page, previous page, page navigation, offset, results

**Import:** `import { Pagination } from '@ahrowe/ui'`  
**Types:** `import type { PaginationProps } from '@ahrowe/ui'`

```tsx
import { useState } from 'react';
import { Pagination } from '@ahrowe/ui';

// Controlled — the usual shape, since the page drives a fetch
const [page, setPage] = useState(1);
<Pagination total={20} page={page} onChange={setPage} />

// Uncontrolled
<Pagination total={20} defaultPage={3} onChange={(page) => console.log(page)} />

// Total pages from a row count
<Pagination total={Math.ceil(rowCount / pageSize)} page={page} onChange={setPage} />

// First/last jump controls outside the previous/next ones
<Pagination total={50} page={page} onChange={setPage} showEdges />

// Page numbers only, no previous/next
<Pagination total={20} page={page} onChange={setPage} showControls={false} />

// Narrower — one page either side of the current one is already the default, `0` shows just the current
<Pagination total={100} page={page} onChange={setPage} siblings={0} />

// Wider window and more pages pinned at each end
<Pagination total={100} page={page} onChange={setPage} siblings={2} boundaries={2} />

// Disabled while the page's data is loading
<Pagination total={20} page={page} onChange={setPage} disabled={isLoading} />
```

**Which pages are shown:** the first and last `boundaries` pages, the current page with `siblings` pages either side, and a `…` wherever a run was collapsed. A gap only appears when it replaces more than one page — collapsing a single page would take the same width and hide a reachable page, so that side lists a fixed-size block instead. With the defaults (`siblings={1}`, `boundaries={1}`) the control is always 7 page slots wide, so it never reflows as the user pages through.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `total` | `number` | Total number of pages (not rows). Renders nothing below `1` |
| `page` | `number` | Controlled current page, 1-based (omit for uncontrolled). Clamped to `1…total` |
| `defaultPage` | `number` | Initial page when uncontrolled (default `1`) |
| `onChange` | `(page: number) => void` | Fires with the new page; not called when the current page is clicked again |
| `siblings` | `number` | Pages shown either side of the current one (default `1`) |
| `boundaries` | `number` | Pages always shown at the start and end (default `1`) |
| `showControls` | `boolean` | Show the previous/next controls (default `true`) |
| `showEdges` | `boolean` | Show first/last jump controls outside the previous/next ones (default `false`) |
| `disabled` | `boolean` | Disables every button |
| `previousIcon` / `nextIcon` | `IconDefinition \| ReactElement` | Override the previous/next icons (default chevrons) |
| `firstIcon` / `lastIcon` | `IconDefinition \| ReactElement` | Override the first/last icons (default double chevrons) |
| `aria-label` | `string` | Accessible name for the `<nav>` (default `'Pagination'`) |

**Accessibility:** renders a `<nav>` of `<button>`s. The current page carries `aria-current="page"`; each page button is labelled `Go to page N`, the controls `Go to previous page` / `Go to next page` / `Go to first page` / `Go to last page`. Previous/next are disabled at the respective end rather than hidden, so the control doesn't reflow. The `…` is `aria-hidden`.

**Theming:** override these CSS variables theme-wide via `ThemeProvider` or per instance via `style`; each falls back to a built-in default:

| Variable | Falls back to |
|----------|---------------|
| `--pagination-active-background` | `var(--primary-color)` |
| `--pagination-active-color` | `var(--text-on-primary)` |
| `--pagination-size` | `36px` |
| `--pagination-gap` | `4px` |

**Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Pagination: { showEdges: true } }}`. See [ConfigProvider.md](ConfigProvider.md).

**Slots:** `root` `item` `control` `ellipsis`

**Labels:** `goToPage(page)` `first` `previous` `next` `last` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `PaginationLabels`.
