---
title: Pagination
description: Pagination provides accessible page navigation for controlled collections.
---

import { meta as PaginationMeta, Default } from '@/content/stories/pagination';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';

export const config = {
	dependencies: {
		components: [Canvas, Demo],
		scripts: [
			'../../../components/component-docs/demo.script.tsx',
			'../../../components/component-docs/canvas.script.tsx',
			'../../../components/component-docs/controls.script.tsx',
		],
	},
};


# Pagination

<p class="docs-lede">Pagination provides accessible page navigation for controlled collections.</p>

## Try it

<Demo of={Default} meta={PaginationMeta} />

## Usage

`RuiPagination` renders previous, numbered, and next controls by default. Listen for `rui-page-change` and update the controlled `page` prop from your data layer.

```tsx
import { RuiPagination } from '@ecopages/radiant-ui/pagination';

<RuiPagination
  label="Search result pages"
  page={page}
  pageCount={pageCount}
  on:rui-page-change={(event) => setPage(event.detail.page)}
/>
```

Pass `children` to replace the default navigation chrome while keeping the `rui-pagination` event contract.

Below `40rem`, or with `class="rui-pagination--compact"`, the default chrome collapses to icon-only previous / next controls around a `Page n of m` position.

The default chrome copy is English. Pass the label view props to localize it:

```tsx
<RuiPagination
  label="Seiten"
  page={page}
  pageCount={pageCount}
  previousText="Zurück"
  previousLabel="Vorherige Seite"
  nextText="Weiter"
  nextLabel="Nächste Seite"
  pageLabel={(page) => `Seite ${page}`}
  statusLabel={(page, pageCount) => `Seite ${page} von ${pageCount}`}
/>
```

## Custom markup

`<rui-pagination>` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required.

```tsx
import '@ecopages/radiant-ui/pagination';

<rui-pagination page={2} page-count={5} label="Search result pages">
  <nav class="rui-pagination__nav" aria-label="Search result pages">
    <button type="button" class="rui-pagination__link" data-pagination-page={1}>
      Previous
    </button>
    <button type="button" class="rui-pagination__link" data-pagination-page={2} aria-current="page">
      2
    </button>
    <button type="button" class="rui-pagination__link" data-pagination-page={3}>
      Next
    </button>
  </nav>
</rui-pagination>
```

BEM classes are presentation-only. Each navigable control needs `data-pagination-page` with a one-based page number.

## Theming

| Part | CSS roles |
| --- | --- |
| Links | Reuses `RuiButton` ghost and filled variants |
| Ellipsis | `on-surface` |

## API

`RuiPagination` wraps `<rui-pagination>` and supplies default navigation controls.

### Attributes (`<rui-pagination>`)

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `Pagination` | Accessible name for the navigation landmark. |
| `page` | `number` | `1` | Current one-based page. |
| `page-count` | `number` | `1` | Total number of pages. |
| `disabled` | `boolean` | `false` | Disable page navigation. |
| `sibling-count` | `number` | `1` | Pages shown on each side of the current page. |

### JSX view props

These props configure the default `RuiPaginationNav` chrome rendered by `RuiPagination` (and accepted by `RuiPaginationNav` directly). They are not custom-element attributes.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `previousText` | `string` | `Previous` | Visible previous-control text; visually hidden in compact chrome. |
| `previousLabel` | `string` | `Go to previous page` | Accessible name of the previous control. |
| `nextText` | `string` | `Next` | Visible next-control text; visually hidden in compact chrome. |
| `nextLabel` | `string` | `Go to next page` | Accessible name of the next control. |
| `pageLabel` | `(page: number) => string` | `Go to page n` | Accessible name of each page-number control. |
| `statusLabel` | `(page: number, pageCount: number) => string` | `Page n of m` | Page position announced (`aria-live="polite"`) in compact chrome. |

### Light-DOM contract

| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-pagination-page]` | yes (per control) | — | `data-pagination-page` (one-based page number) |

Nested hosts: none.

### Events

| Event | Detail | Description |
| --- | --- | --- |
| `rui-page-change` | `{ page: number }` | Emitted when a page control is activated. |

### View helpers

| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiPagination` | `<rui-pagination>` | Renders `RuiPaginationNav` by default. |
| `RuiPaginationNav` | `[data-pagination-page]` on each link | Previous, numbered, and next controls. |

### CSS classes

| Class | Description |
| --- | --- |
| `.rui-pagination` | Navigation root. |
| `.rui-pagination__list` | Page controls list. |
| `.rui-pagination__link` | Previous, next, and page control. |
| `.rui-pagination__page` | Page-number item; `__page--current` marks the active page. |
| `.rui-pagination--compact` | Force the chrome used below `40rem`: icon-only previous / next around the page position. |
| `.rui-pagination__status` | Wrapper for the compact page-position label. |
| `.rui-pagination__status-label` | Muted compact label (`statusLabel`, default `Page n of m`); hidden when numbered pages are shown. |
| `.rui-pagination__ellipsis` | Hidden range marker between page numbers. |
