Pagination
A page navigator: previous/next controls around a windowed list of page numbers with ellipses.
Live demo
Pagination walks a reader through a paged collection. It is a <nav> landmark wrapping previous and next controls and an ordered list of page numbers; the current page is marked aria-current="page", and a sibling window around it keeps the control compact, collapsing the gaps to an ellipsis when there are more pages than fit.
The visible range is computed from page and total plus two knobs — siblings (links on each side of the current page) and boundaries (links pinned at each end). Give it an href template containing {page} and every page renders as a real link, so the control navigates with zero JavaScript and works on the static Astro path; omit the template and the pages render as buttons that emit a page-change event carrying the chosen page. A tone colors the current-page pill and three sizes scale the type.
When to use
How this component composes with the rest of the set.
Props
8 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Variants
accent
Current-page pill in the accent tone (the default).
neutral
Current-page pill in neutral ink for a quieter control.
Sizes
sm
Compact.
md
Default.
lg
Large.
States
hover
Pointer over a page or control; a soft wash fills behind it.
focus-visible
Keyboard focus: a token-colored ring, plus a transparent outline promoted to a real one in forced-colors mode.
current
The active page: a filled tone pill, no hover, aria-current.
disabled
A previous/next control at the end of the range: dimmed and non-interactive.
Anatomy
The named parts that make up the component, with their selectors.
pagination
The <nav> landmark laying the controls and page list out in a wrapping row.
page
A page number — a link (href mode) or button — with a hover wash and a focus ring.
current
The current page: a filled pill in the tone color, carrying aria-current.
control
The previous / next arrows; dimmed and inert at the ends of the range.
ellipsis
The collapsed-gap marker between distant pages, hidden from assistive tech.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xoji-default register
(derive(xojiDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--accent-2
--accent-2-fg
--accent-3
--accent-3-fg
--accent-4
--accent-4-fg
--accent-fg
--bg-2
--black
--black-fg
--blue
--blue-fg
--border-normal
--border-thick
--border-thin
--brown
--brown-fg
--cyan
--cyan-fg
--danger
--danger-fg
--duration-fast
--ease-standard
--fg-1
--fg-2
--fg-3
--font-sans
--gray
--gray-fg
--green
--green-fg
--info
--info-fg
--leading-normal
--neutral
--neutral-fg
--orange
--orange-fg
--pink
--pink-fg
--purple
--purple-fg
--radius-sm
--red
--red-fg
--ring
--space-0
--space-1
--success
--success-fg
--text-body
--text-sm
--text-xs
--warn
--warn-fg
--weight-medium
--white
--white-fg
--yellow
--yellow-fg
Accessibility
Code
Link and button modes
An href template makes each page a zero-JS link; without it the pages are buttons that emit page-change.
<!-- link mode: each page is a real anchor, zero JS needed -->
<xoji-pagination page="3" total="20" href="/blog?page={page}"></xoji-pagination>
<!-- button mode: listen for page-change -->
<xoji-pagination page="3" total="20" tone="neutral"></xoji-pagination>
<script>
document.querySelector("xoji-pagination:not([href])")
.addEventListener("page-change", (e) => console.log(e.detail.page));
</script>
<script lang="ts">
import { Pagination } from "@xoji/svelte";
let page = $state(3);
</script>
<Pagination {page} total={20} onpagechange={(e) => (page = e.detail.page)} />
---
import Pagination from "@xoji/astro/Pagination.astro";
const page = Number(Astro.url.searchParams.get("page") ?? 1);
---
<Pagination page={page} total={20} href="/blog?page={page}" />