# Pagination

Page navigation as real links. CSS only. (CSS only.)

Reference: https://timeless.build/docs/components/pagination/

> **Choosing between components.** This paginates *pages* — one URL each. To page through the options inside a [Listbox](/docs/components/listbox/), [Select](/docs/components/select/), or [Combobox](/docs/components/combobox/), set `page-size` on that element and author its `pager` parts instead; nothing navigates there, so links would be wrong.

> **Markup you author.** Give the `<nav>` an `aria-label`. Author every page but the current one as an `<a href>`, the current one as a `<span aria-current="page">`, and the ellipsis as `aria-hidden="true"`. Previous and Next need accessible names that say what they do, and at the first or last page they become `<span>` elements — an `<a aria-disabled="true">` still navigates, so it is a lie. Compose `<ul class="ui-group" data-ui-attached>` for a joined strip.

## Markup

```html
<nav class="ui-pagination" aria-label="Pagination">
  <ul>
    <li data-ui-part="item"><a data-ui-part="previous" href="?page=3" rel="prev">Previous</a></li>
    <li data-ui-part="item"><a data-ui-part="link" href="?page=1" aria-label="Page 1">1</a></li>
    <li data-ui-part="item"><span data-ui-part="ellipsis" aria-hidden="true">&hellip;</span></li>
    <li data-ui-part="item"><a data-ui-part="link" href="?page=3" aria-label="Page 3">3</a></li>
    <li data-ui-part="item"><span data-ui-part="link" aria-current="page">4</span></li>
    <li data-ui-part="item"><a data-ui-part="link" href="?page=5" aria-label="Page 5">5</a></li>
    <li data-ui-part="item"><span data-ui-part="ellipsis" aria-hidden="true">&hellip;</span></li>
    <li data-ui-part="item"><a data-ui-part="link" href="?page=12" aria-label="Page 12">12</a></li>
    <li data-ui-part="item"><a data-ui-part="next" href="?page=5" rel="next">Next</a></li>
  </ul>
</nav>
```

## Install

```js
import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/pagination.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/pagination.css'
```

## Anatomy

Parts are authored in your own markup and identified by the selector below. Required parts
must be present for the component to work. Private `data-ui-internal-*` hooks are written by
the runtime and must never be authored.

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `item` | No | `[data-ui-part~='item']` | One cell wrapper. Use `<li>`. |
| `link` | No | `[data-ui-part~='link']` | A page cell. Use `<a href>` for every page but the one you are on, and a `<span aria-current="page">` for that one. |
| `previous` | No | `[data-ui-part~='previous']` | Steps back one page. Give it an accessible name that says so; `‹` alone names nothing. At the first page, render it as a `<span>` rather than a disabled link. |
| `next` | No | `[data-ui-part~='next']` | Steps forward one page, with the same two rules. |
| `ellipsis` | No | `[data-ui-part~='ellipsis']` | Stands for the pages omitted from a long range. Mark it `aria-hidden="true"`: the gap between page 3 and page 40 is not a page. |

## Attributes

Every value below is implemented by the stylesheets this component ships. Boolean attributes
are presence-based: author the attribute with no value, or omit it.

| Attribute | Values | Default | Description |
| --- | --- | --- | --- |
| `data-ui-size` | `sm` · `md` · `lg` | `md` | Cell height, padding, and font size. |

## State

Native attributes, ARIA, and platform pseudo-classes are authoritative. Style state through
the selectors below rather than adding your own state classes.

| State | Source | How to express it |
| --- | --- | --- |
| `current` | aria | Author `aria-current="page"` on the cell for the page being shown. Timeless never writes it. |

## Styling

Root identity: `ui-pagination`.
Required stylesheets: `tokens.css`, `core/pagination.css`, `themes/atmosphere/tokens.css`, `themes/atmosphere/pagination.css`.

Set these custom properties to restyle the component:

| Custom property | Controls |
| --- | --- |
| `--ui-pagination-gap` | Gap between cells. |
| `--ui-pagination-cell-size` | Minimum height and width of a cell. |

Design tokens this component reads (17), global and set at the theme level: `--ui-accent`, `--ui-bg-accent`, `--ui-bg-control-active`, `--ui-bg-control-hover`, `--ui-bg-surface`, `--ui-duration-fast`, `--ui-ease-standard`, `--ui-fg`, `--ui-fg-muted`, `--ui-fg-subtle`, `--ui-focus`, `--ui-line`, `--ui-radius-control`, `--ui-shadow-control-active`, `--ui-shadow-outline-control`, `--ui-space-1`, `--ui-space-3`.

## Accessibility

Keep the native elements, roles, and relationships shown in the markup. Timeless adds only the
state and keyboard coordination the platform does not already provide, and it never supplies
your accessible names — those depend on your content.

The ARIA Authoring Practices Guide has no pattern for Pagination. The contract below is a composition of behavior the platform already defines, not ARIA invented to fill the gap.

The APG has no pagination pattern, so this documents a composition: a `<nav>` with an `aria-label`, a list, and one link per page. They are links rather than buttons on purpose — a page is a URL, so it is shareable, middle-clickable, and in the back button’s history, none of which a click handler gives you. That has one consequence worth stating: **a disabled link is not a thing.** At the first or last page, render Previous or Next as a `<span>`, not an `<a aria-disabled="true">` that still navigates. Compose `<ul class="ui-group" data-ui-attached>` for a joined strip.

## Before JavaScript runs

This primitive is CSS only. There is nothing to register and nothing to wait for.
