<!-- Protocore Design System — Pagination -->
# Pagination

- **Category:** Navigation (`navigation`)
- **Slug:** `navigation/pagination`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Pagination } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/navigation/pagination

> Page navigation — a mono button row. Numbered mode draws pages with ellipses and an inverted current page; cursor mode draws prev/next only.

## When to use it

Use **Pagination** to move through pages of a list or table under a fixed page size.

- Know the total count? Use **numbered** mode (`pageCount`) so users can jump directly and see how far they are.
- Backed by a cursor / keyset API with no cheap total? Use **cursor** mode (`hasPrev`/`hasNext`) — never fake a page count.
- Prefer infinite scroll for exploratory feeds; keep Pagination for addressable, bookmarkable pages (search results, ledgers, logs).
- It pairs naturally under a **DataTable** or **ListingRow** list.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `aria-label` | `string` | no | — | Accessible name for the nav landmark. Default "Pagination". |
| `className` | `string` | no | — | — |
| `hasNext` | `boolean` | no | — | Cursor mode: whether a next page exists. Ignored when `pageCount` is set. |
| `hasPrev` | `boolean` | no | — | Cursor mode: whether a previous page exists. Ignored when `pageCount` is set. |
| `onPageChange` | `((page: number) => void)` | no | — | Called with the requested 1-based page. |
| `page` | `number` | yes | — | Current page, 1-based. |
| `pageCount` | `number` | no | — | Total page count. Omit for cursor mode (prev/next only). |
| `siblingCount` | `number` | no | `1` | Numbered mode: pages to keep either side of the current page. Default 1. |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Numbered (known total)

Pass `pageCount` for a fully numbered row. The current page inverts to a solid fill, and long ranges collapse to ellipses. Emit changes through `onPageChange`.

```tsx
import { useState } from "react";
import { Pagination, Text } from "@protocore/pds";

export default function PaginationNumbered() {
  const [page, setPage] = useState(3);
  return (
    <div>
      <Text>
        Showing settlements {(page - 1) * 25 + 1}–{page * 25} of 300.
      </Text>
      <div style={{ marginTop: "var(--pds-space-4)" }}>
        <Pagination page={page} pageCount={12} onPageChange={setPage} />
      </div>
    </div>
  );
}
```

### Cursor (unknown total)

Omit `pageCount` and pass `hasPrev` / `hasNext` for keyset-paginated data where the server never returns a total — you only know whether another page exists.

```tsx
import { useState } from "react";
import { Pagination, Text } from "@protocore/pds";

// Cursor mode: the API only tells us whether more pages exist, not the total —
// the natural fit for keyset-paginated event logs.
export default function PaginationCursor() {
  const [page, setPage] = useState(1);
  const totalKnown = 5; // stand-in for the server's hasNext signal
  return (
    <div>
      <Text>Message log — page {page}</Text>
      <div style={{ marginTop: "var(--pds-space-4)" }}>
        <Pagination
          page={page}
          hasPrev={page > 1}
          hasNext={page < totalKnown}
          onPageChange={setPage}
        />
      </div>
    </div>
  );
}
```

## Do & don't

**Do**

- Use cursor mode (hasPrev/hasNext) whenever the total is unknown or expensive to compute.
- Keep page size fixed while paging so offsets stay meaningful.
- Reflect the current page in the URL so a page is shareable and back-button-safe.
- Disable PREV/NEXT at the ends rather than hiding them, so the control's width is stable.

**Don't**

- Don't invent a pageCount you don't have just to show numbers.
- Don't reset to page 1 silently when a filter changes without telling the user.
- Don't stack Pagination with infinite scroll on the same list.
- Don't raise siblingCount so high the row overflows on mobile.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Move focus across PREV, the page buttons, and NEXT |
| `Enter / Space` | Activate the focused page button |

**Notes**

- The root is a `<nav aria-label="Pagination">` landmark; override the name with `aria-label` when several paginators share a page.
- The current page button carries `aria-current="page"`; PREV / NEXT have explicit `aria-label`s and are `disabled` at the ends.

## Related

`data-table`, `breadcrumb`, `filter-bar`, `steps`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
