# Pagination

> Page links with prev/next and ellipses for long page ranges.

URL: https://apogee.avaui.dev/docs/pagination
Last updated: 2026-10-15

Page links with prev/next and ellipses for long page ranges.

Last updated October 15, 2026

```astro
<Pagination currentPage={4} totalPages={10} />
```

## Add

```sh
npx @ava-ui/apogee add pagination
```

No Apogee component dependencies. Ships `@lucide/astro` for the prev/next chevrons. Zero JS — plain links.

## Usage

```astro
---
import Pagination from '@/components/apogee/Pagination/Pagination.astro'
---
<Pagination currentPage={1} totalPages={5} />
```

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root `<nav>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `currentPage` | `number` | — | Current page, 1-indexed. |
| `totalPages` | `number` | — | Total number of pages. |
| `getHref` | `(page: number) => string` | `` (p) => `?page=${p}` `` | Builds each page link’s `href`. |
| `previousLabel` / `nextLabel` | `string` | `'Previous page'` / `'Next page'` | Accessible names of the arrows, or their visible text with `labels` (localise them). |
| `labels` | `boolean` | `false` | Shows the labels beside the arrows from `md`; below it they stay as screen-reader text. |
| `rounded` | `boolean` | `false` | Round page cells. |
| `summary` | `string` | `'Page {current} of {total}'` | The phone summary; `{current}` and `{total}` are replaced (localise it). |
| `class` | `string` | — | Extra classes on the root `<nav>`, merged last. |

Page list shown: always seven cells from seven pages up (first, last, a three-page run around the current page, and an ellipsis or the neighbour on each side), so the row never changes width as the user pages through.

## Constant width

With seven or more pages the row always has seven cells, so it never jumps as the user pages through.

```astro
<Pagination currentPage={1} totalPages={20} />
<Pagination currentPage={5} totalPages={20} />
<Pagination currentPage={20} totalPages={20} />
```

## Phones

Below `md` the page numbers step aside and a “Page 5 of 20” summary sits between the arrows. Resize the window to see it.

## Labels and rounded cells

```astro
<Pagination currentPage={3} totalPages={10} labels previousLabel="Previous" nextLabel="Next" />
<Pagination currentPage={3} totalPages={10} rounded />
```

Localised summary (`summary` takes `{current}` and `{total}`):

```astro
<Pagination currentPage={3} totalPages={10} summary="Pagina {current} din {total}" />
```

## States

First page — the prev control is a disabled, non-interactive `<span>`:

```astro
<Pagination currentPage={1} totalPages={10} />
```

Last page — same treatment on next:

```astro
<Pagination currentPage={10} totalPages={10} />
```

Few pages — no ellipsis needed:

```astro
<Pagination currentPage={2} totalPages={3} />
```

Current page in the middle of a long range — ellipses on both sides:

```astro
<Pagination currentPage={12} totalPages={24} />
```

## Examples

A table footer with a page-size select, all zero JS: the form submits with GET and the server renders the new page.

```astro
<form method="get" class="flex flex-wrap items-center justify-between gap-3 border-t border-neutral px-4 py-3">
  <label class="flex items-center gap-2 text-sm text-neutral-faded">
    Rows per page
    <select name="per_page" onchange="this.form.requestSubmit()">…</select>
  </label>
  <Pagination currentPage={page} totalPages={pages} getHref={(p) => `?page=${p}&per_page=${perPage}`} />
</form>
```

Route-based links instead of a query param:

```astro
<Pagination currentPage={2} totalPages={8} getHref={(p) => `/blog/page/${p}`} />
```

## Accessibility

-   `<nav aria-label="Pagination">` landmark.
-   Every page is a real `<a>` — keyboard and AT navigable with no custom key handling.
-   Current page gets `aria-current="page"`.
-   Disabled edges (`currentPage === 1` / `=== totalPages`) render as `<span aria-hidden="true" aria-disabled="true">`, not links: they hold their place (and their label with `labels`) but there is nothing to focus or announce.
-   `rel="prev"` / `rel="next"` on the adjacent links hint the sequential relationship to browsers and AT.
-   Chevron icons are `aria-hidden`; the links carry `aria-label="Previous page"` / `"Next page"`.
-   No JS — server-rendered links, identical behaviour with JS off.

## Tokens

| Token | Used for |
| --- | --- |
| `--radius-md` | Cell corner radius |
| `--outline-color-focus` | Focus ring |
| `--background-neutral-faded-highlighted` | Hover background |
| `--background-primary` / `--on-primary` | Current-page fill |
| `--foreground-neutral` | Link text |
| `--foreground-neutral-faded` / `--foreground-disabled` | Ellipsis / disabled-edge colour |

## Do / Don’t

-   **Do** keep `getHref` pure — a real, navigable, crawlable URL, not a client-only stub.
-   **Do** use it for genuine paginated routes/lists, not to fake a “load more” interaction.
-   **Don’t** intercept the links with a click handler for client-side-only paging — it breaks no-JS and deep-linking.
-   **Don’t** render it when there’s only one page.

## Content

-   No editable copy — page numbers are the content, kept `tabular-nums` so digit widths stay equal as the current page changes.

## Related

[

BreadcrumbsA path trail showing where the current page sits in the site hierarchy.

](https://apogee.avaui.dev/docs/breadcrumbs)[

DataGridA data table with client-side column sorting and optional row selection.

](https://apogee.avaui.dev/docs/data-grid)[

StepsA vertical progress list for onboarding, checkout, and wizard flows.

](https://apogee.avaui.dev/docs/steps)
