registry:ui · NativeHTML
pagination — a nav of real links
W3C-first, composition-first: the nav landmark hosts composed parts — PaginationContent (ul), PaginationItem (li), PaginationLink (real hrefs or an onclick button, aria-current when active), Previous/Next at the edges. The page-window math lives in the exported pure helper pageRange with its current/total/siblings options: sticky edges, siblings around the current page, and two ellipsis TOKENS the consumer branches on. At the bounds Previous/Next become honest disabled spans — a link that goes nowhere is a lie to every input mode.
pagination
pageRange drives the window; the href template decides where page N lives — this demo routes back to the page itself. Watch the window slide and the ellipses collapse near the edges.
<script lang="ts">
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
pageRange,
} from '@ui/pagination/index';
</script>
<!-- the window math is a pure helper; the links are yours -->
{#each pageRange({ current: page, total: pages }) as page (page)}
{#if page === 'ellipsis-start' || page === 'ellipsis-end'}
<PaginationItem><PaginationEllipsis /></PaginationItem>
{:else}
<PaginationItem>
<PaginationLink {page} isActive={page === current} href={href(page)} />
</PaginationItem>
{/if}
{/each}composition
The window, as evidence
pageRange with its current/total/siblings options — sticky edges, siblings around the current page, ellipsis tokens only when something was actually collapsed. The helper returns numbers interleaved with 'ellipsis-start' | 'ellipsis-end'; consumers branch on the tokens and render PaginationEllipsis. Same math as the closed component, ported exactly.
- page 1 / 30
- 1 2 … 30
- page 5 / 30
- 1 … 4 5 6 … 30
- page 12 / 30
- 1 … 11 12 13 … 30
- page 30 / 30
- 1 … 29 30
- page 2 / 4
- 1 2 3 4
<script lang="ts">
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
pageRange,
} from '@ui/pagination/index';
</script>
<!-- the window math is a pure helper; the links are yours -->
{#each pageRange({ current: page, total: pages }) as page (page)}
{#if page === 'ellipsis-start' || page === 'ellipsis-end'}
<PaginationItem><PaginationEllipsis /></PaginationItem>
{:else}
<PaginationItem>
<PaginationLink {page} isActive={page === current} href={href(page)} />
</PaginationItem>
{/if}
{/each}types
Composable pagination parts
The nav root provides the landmark; small parts keep list structure, links, edges, and gaps semantically explicit.
Pagination
nav landmark and density root
PaginationContent
ul list container
PaginationItem
li list item
PaginationLink
numbered page link or button
PaginationPrevious / Next
edge controls or disabled spans
PaginationEllipsis
decorative collapsed range
usage
Render a page window
Use pageRange for the pure window calculation, then map its page numbers and ellipsis tokens to composed pagination parts.
<script lang="ts">
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
pageRange,
} from '@ui/pagination/index';
</script>
<!-- the window math is a pure helper; the links are yours -->
{#each pageRange({ current: page, total: pages }) as page (page)}
{#if page === 'ellipsis-start' || page === 'ellipsis-end'}
<PaginationItem><PaginationEllipsis /></PaginationItem>
{:else}
<PaginationItem>
<PaginationLink {page} isActive={page === current} href={href(page)} />
</PaginationItem>
{/if}
{/each}a11y
Honest pagination controls
Every destination stays a real link, the active page is announced, and unavailable edges render as disabled text rather than dead links.
Keyboard
| Key | Action |
|---|---|
| Tab | Move through page and edge controls |
| Enter | Follow a numbered or edge link |
| Space | Activate an onclick-only page button |
ARIA
| Attribute | Value | Description |
|---|---|---|
| aria-label | 'Pagination' by default | Names the navigation landmark. |
| aria-current | 'page' on active link | Announces the current page. |
| aria-disabled | true at bounds | Marks unavailable Previous and Next controls. |
| aria-hidden | true on ellipsis | Keeps collapsed-range decoration out of the reading order. |
theming
Density-aligned page chips
List gaps, link targets, and chip labels all consume the shared density scale; press tokens give interactive pages their elevation pose.
| Token | Default | Source |
|---|---|---|
| --jx-gap | 8 / 8 / 12 / 16px | density |
| --jx-hit | 28 / 32 / 40 / 48px | density |
| --jx-inset | 8 / 8 / 12 / 16px | density |
| --jx-text | 11 / 12 / 13 / 15px | density |
| --jx-line | 16 / 18 / 20 / 24px | density |
| --jx-press-shadow-hover | var(--shadow-xs) | component |
| --jx-press-shadow-active | var(--shadow-xs-press) | component |
usage
Usage
Import the family parts and compose them in markup — the full usage file, as the canvas above runs it.
<script lang="ts">
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
pageRange,
} from '@ui/pagination/index';
</script>
<!-- the window math is a pure helper; the links are yours -->
{#each pageRange({ current: page, total: pages }) as page (page)}
{#if page === 'ellipsis-start' || page === 'ellipsis-end'}
<PaginationItem><PaginationEllipsis /></PaginationItem>
{:else}
<PaginationItem>
<PaginationLink {page} isActive={page === current} href={href(page)} />
</PaginationItem>
{/if}
{/each}axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. The landmark nav carries no own — the size axis scales the root, the link parts ride plain inheritance.
Pagination · universal props
<Pagination size={18} density="small">…</Pagination>api
Props
The root and links carry the core public contract; composition supplies list structure and page-window policy.
Pagination
| Property | Type | Default | Description |
|---|---|---|---|
| label | string | 'Pagination' | Accessible nav landmark name. |
| children * | Snippet | — | Composed pagination parts. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |
PaginationLink
| Property | Type | Default | Description |
|---|---|---|---|
| page * | number | — | Page number and default visible label. |
| isActive | boolean | false | Applies active paint and aria-current=page. |
| href | string | — | Destination; omit only for an onclick-only button. |
| onclick | (event: MouseEvent) => void | — | Click-only page action. |
| child | Snippet | — | Optional replacement anchor element. |
PaginationPrevious and PaginationNext
| Property | Type | Default | Description |
|---|---|---|---|
| href | string | — | Destination; omission renders an honest disabled span. |
| onclick | (event: MouseEvent) => void | — | Click-only edge action. |
| children | Snippet | default label | Replacement edge label. |