Pagination
Page links with prev/next and ellipses for long page ranges.
Last updated
Show codeHide code
<Pagination currentPage={4} totalPages={10} />Add
npx @ava-ui/apogee add pagination
No Apogee component dependencies. Ships @lucide/astro for the prev/next
chevrons. Zero JS — plain links.
Usage
---
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.
Show codeHide code
<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
Show codeHide code
<Pagination currentPage={3} totalPages={10} labels previousLabel="Previous" nextLabel="Next" />
<Pagination currentPage={3} totalPages={10} rounded />Localised summary (summary takes {current} and {total}):
Show codeHide code
<Pagination currentPage={3} totalPages={10} summary="Pagina {current} din {total}" />States
First page — the prev control is a disabled, non-interactive <span>:
Show codeHide code
<Pagination currentPage={1} totalPages={10} />Last page — same treatment on next:
Show codeHide code
<Pagination currentPage={10} totalPages={10} />Few pages — no ellipsis needed:
Show codeHide code
<Pagination currentPage={2} totalPages={3} />Current page in the middle of a long range — ellipses on both sides:
Show codeHide code
<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.
<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:
<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 withlabels) 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 carryaria-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
getHrefpure — 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-numsso digit widths stay equal as the current page changes.