Accessible, unstyled Pagination primitive with controlled page state and SSR-stable ids.
|
|
| Package |
@vizejs/ui/pagination |
| Maturity |
stable |
| Own the source |
vize lib pull pagination |
| Requires |
context, controllable-state, id |
| Aliases |
pagination, pager, page navigation, page controls |
| Covers |
HTML nav landmark, WAI-ARIA aria-current page, MUI Pagination, Vuetify Pagination, Reka UI Pagination |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Pagination, PaginationRoot, PaginationEllipsis, PaginationItem, PaginationList, PaginationNext, PaginationPage, PaginationPrevious } from "@vizejs/ui/pagination";
Or copy the source into your project with vize lib pull pagination (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Search results pagination bound with v-model that collapses distant pages into ellipses. -->
<script setup lang="ts">
import { ref } from "vue";
import {
PaginationEllipsis,
PaginationItem,
PaginationList,
PaginationNext,
PaginationPage,
PaginationPrevious,
PaginationRoot,
} from "@vizejs/ui/pagination";
const page = ref(5);
</script>
<template>
<div>
<p>Showing results {{ (page - 1) * 20 + 1 }}–{{ page * 20 }} of 240</p>
<PaginationRoot v-slot="{ range }" v-model="page" :page-count="12" label="Search results pages">
<PaginationList>
<PaginationItem><PaginationPrevious>Previous</PaginationPrevious></PaginationItem>
<template v-for="item in range" :key="item.key">
<PaginationItem v-if="item.type === 'page'" :page="item.page">
<PaginationPage :page="item.page">{{ item.page }}</PaginationPage>
</PaginationItem>
<PaginationItem v-else>
<PaginationEllipsis :position="item.position" />
</PaginationItem>
</template>
<PaginationItem><PaginationNext>Next</PaginationNext></PaginationItem>
</PaginationList>
</PaginationRoot>
</div>
</template>
API
Source: src/families/navigation/pagination/pagination.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned pagination id. null and undefined select a deterministic fallback. |
pageCount (required) |
number |
required |
Total number of pages. Runtime values below one resolve to one page. |
modelValue |
number |
undefined |
Controlled current page. undefined selects uncontrolled behavior. |
defaultValue |
number |
1 |
Initial page for uncontrolled use and the page restored by reset. |
disabled |
boolean |
false |
Disable all page controls. |
label |
string |
"Pagination" |
Accessible landmark label mirrored to aria-label. |
siblingCount |
number |
1 |
Number of pages kept on each side of the current page in range. |
boundaryCount |
number |
1 |
Number of pages always kept at each boundary in range. |
as |
PrimitiveAs |
"nav" |
Native element, custom element, or component to render. |
Events
| Event |
Payload |
Description |
update:modelValue |
[page: number] |
Fired when the current page requests a new controlled value. |
change |
[page: number, previous: number, nativeEvent: Event | null] |
Fired after any distinct current-page request. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationSlotState |
Compound Pagination children. Receives current page, range, and boundary state. |
Exposed
| Member |
Type |
Description |
canNext |
— |
|
canPrevious |
— |
|
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
goNext |
— |
|
goPrevious |
— |
|
id |
— |
|
listId |
— |
|
nextPage |
— |
|
page |
— |
|
pageCount |
— |
|
previousPage |
— |
|
range |
— |
|
reset |
— |
|
setPage |
— |
|
state |
— |
|
Source: src/families/navigation/pagination/pagination.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned pagination id. null and undefined select a deterministic fallback. |
pageCount (required) |
number |
required |
Total number of pages. Runtime values below one resolve to one page. |
modelValue |
number |
undefined |
Controlled current page. undefined selects uncontrolled behavior. |
defaultValue |
number |
1 |
Initial page for uncontrolled use and the page restored by reset. |
disabled |
boolean |
false |
Disable all page controls. |
label |
string |
"Pagination" |
Accessible landmark label mirrored to aria-label. |
siblingCount |
number |
1 |
Number of pages kept on each side of the current page in range. |
boundaryCount |
number |
1 |
Number of pages always kept at each boundary in range. |
as |
PrimitiveAs |
"nav" |
Native element, custom element, or component to render. |
Events
| Event |
Payload |
Description |
update:modelValue |
[page: number] |
Fired when the current page requests a new controlled value. |
change |
[page: number, previous: number, nativeEvent: Event | null] |
Fired after any distinct current-page request. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationSlotState |
Compound Pagination children. Receives current page, range, and boundary state. |
Exposed
| Member |
Type |
Description |
page |
number |
Current page clamped into the available page range. |
pageCount |
number |
Total page count normalized to at least one page. |
disabled |
boolean |
Whether every page control is disabled by the root. |
canPrevious |
boolean |
Whether a previous page exists and controls are enabled. |
canNext |
boolean |
Whether a next page exists and controls are enabled. |
previousPage |
number | null |
Previous page number, or null while on the first page. |
nextPage |
number | null |
Next page number, or null while on the last page. |
range |
readonly PaginationRangeItem[] |
Deterministic compact range for rendering page and ellipsis items. |
state |
PaginationState |
Stable state token for styling and tests. |
element |
PrimitiveElement | null |
Rendered landmark element or component instance. |
id |
string |
Root-owned base id for the Pagination family. |
listId |
string |
Id wired to PaginationList. |
focus |
(options?: FocusOptions) => void |
Move focus to the current page control when it is rendered. |
setPage |
(page: number, event?: Event | null) => boolean |
Request a current-page update and report whether it differs. |
goPrevious |
(event?: Event | null) => boolean |
Request the previous page and report whether it differs. |
goNext |
(event?: Event | null) => boolean |
Request the next page and report whether it differs. |
reset |
() => boolean |
Restore the default page and report whether it differs. |
Source: src/families/navigation/pagination/pagination-ellipsis.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"span" |
Native element, custom element, or component to render. |
position |
PaginationEllipsisPosition |
"end" |
Position relative to the current compact range. |
label |
string |
"More pages" |
Accessible label for the non-interactive gap. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationEllipsisSlotState |
Ellipsis contents. Receives position and the literal disabled state. |
Exposed
| Member |
Type |
Description |
position |
PaginationEllipsisPosition |
Ellipsis position relative to the current compact range. |
disabled |
true |
Ellipses are never interactive. |
element |
PrimitiveElement | null |
Rendered ellipsis element or component instance. |
Source: src/families/navigation/pagination/pagination-item.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"li" |
Native element, custom element, or component to render. |
page |
number |
undefined |
Page represented by the item. Omit for previous, next, or ellipsis wrappers. |
disabled |
boolean |
false |
Disable this item for styling state. Interactive child controls own activation. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationItemSlotState |
Item contents. Receives current-page and disabled state. |
Exposed
| Member |
Type |
Description |
page |
number | undefined |
Page represented by the item, or undefined for structural items. |
current |
boolean |
Whether the item represents the current page. |
disabled |
boolean |
Whether the item or root is disabled. |
state |
PaginationPageState |
Stable state token for styling and tests. |
element |
PrimitiveElement | null |
Rendered list item element or component instance. |
Source: src/families/navigation/pagination/pagination-list.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"ol" |
Native element, custom element, or component to render. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationListSlotState |
Pagination item children. Receives current page, range, and list id. |
Exposed
| Member |
Type |
Description |
page |
number |
Current page clamped into the available page range. |
pageCount |
number |
Total page count normalized to at least one page. |
disabled |
boolean |
Whether every page control is disabled by the root. |
canPrevious |
boolean |
Whether a previous page exists and controls are enabled. |
canNext |
boolean |
Whether a next page exists and controls are enabled. |
previousPage |
number | null |
Previous page number, or null while on the first page. |
nextPage |
number | null |
Next page number, or null while on the last page. |
range |
readonly PaginationRangeItem[] |
Deterministic compact range for rendering page and ellipsis items. |
state |
PaginationState |
Stable state token for styling and tests. |
listId |
string |
Deterministic id assigned to the list. |
element |
PrimitiveElement | null |
Rendered list element or component instance. |
focus |
(options?: FocusOptions) => void |
Move focus to the current page control when it is rendered. |
Source: src/families/navigation/pagination/pagination-next.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Disable this navigation control. |
ariaLabel |
string |
"Go to next page" |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label this navigation control. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe this navigation control. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before this control requests the next page. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationControlSlotState |
Next control contents. Receives target page, disabled, and state tokens. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
id |
— |
|
select |
— |
|
state |
— |
|
targetPage |
— |
|
Source: src/families/navigation/pagination/pagination-page.vue
Props
| Prop |
Type |
Default |
Description |
page (required) |
number |
required |
Page selected by this control. |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Disable this page control. |
ariaLabel |
string |
computed from page state |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label this page control. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe this page control. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before this control requests page selection. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationPageSlotState |
Page control contents. Receives current, disabled, and state tokens. |
Exposed
| Member |
Type |
Description |
page |
number |
Page selected by this control. |
current |
boolean |
Whether this control represents the current page. |
disabled |
boolean |
Whether this control is disabled by itself, the root, or page bounds. |
state |
PaginationPageState |
Stable state token for styling and tests. |
element |
HTMLButtonElement | null |
Rendered native page button. |
id |
string |
Deterministic id assigned to the page control. |
focus |
(options?: FocusOptions) => void |
Move focus to the page button. |
select |
() => boolean |
Select this page and report whether the current page changed. |
Source: src/families/navigation/pagination/pagination-previous.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Disable this navigation control. |
ariaLabel |
string |
"Go to previous page" |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label this navigation control. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe this navigation control. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before this control requests the previous page. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
PaginationControlSlotState |
Previous control contents. Receives target page, disabled, and state tokens. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
id |
— |
|
select |
— |
|
state |
— |
|
targetPage |
— |
|
Behavior
Normative state x input -> outcome table for pagination.vue,
pagination-list.vue, pagination-item.vue, pagination-page.vue,
pagination-previous.vue, pagination-next.vue, and
pagination-ellipsis.vue (@vizejs/ui/pagination). Every row is proven by
focused tests plus package export, family catalog, renderer, runtime
conformance, and size-budget gates.
| ID |
State |
Input |
Outcome |
Evidence |
| P1 |
default / current page |
render |
root, list, controls, page items, ellipses, slots, deterministic ids, ARIA, and data land |
renders accessible pagination semantics with deterministic ids and range |
| P2 |
uncontrolled |
page / next click |
internal page changes, update:modelValue and change emit once per distinct request |
clicks update uncontrolled page state and suppress current-page repeats |
| P3 |
controlled |
page click |
emits requested page while rendered current page waits until the parent accepts it |
controlled page wins until the parent accepts the request |
| P4 |
disabled / boundary |
click, Space, or Tab |
unavailable controls are native-disabled, leave tab order, and do not emit page changes |
disabled roots and boundary controls suppress activation and tab focus |
| P5 |
exposed instances |
focus, setPage, reset |
public refs expose live state and imperative focus/page methods |
exposes typed state and imperative page controls |
| P6 |
missing provider |
setup |
compound parts fail closed with the shared context diagnostic |
compound parts require a matching root provider |
| P7 |
range helper |
compact range render |
boundary/sibling windows are deterministic and one-page gaps expand without ellipses |
renders accessible pagination semantics with deterministic ids and range |
| P8 |
out-of-range page |
render |
invalid page controls are disabled and receive non-colliding deterministic ids |
out-of-range page controls stay disabled without duplicating valid page ids |
| P9 |
SSR and hydration |
isolated render/mount |
generated ids are byte-identical per request and hydrate without replacement warnings |
pagination-ssr.test.ts |
The primitive renders no styling. PaginationPage, PaginationPrevious, and
PaginationNext are native buttons so keyboard activation follows the platform.
PaginationPage uses aria-current="page" for the current page while keeping
the current page focusable; root-disabled, boundary, and out-of-range controls
use native disabled.