Skip to content

Pagination — the cross-framework headless pager

Pagination is a headless, fully-accessible pagination control with no third-party engine behind it. It covers the whole windowed page-item model: numbers interleaved with ellipses, configurable sibling/boundary windowing, the prev/next bounds, roving keyboard navigation, the <nav> landmark with aria-current="page" on the active control, and the two-way page binding. The same component ships for React, Vue, Svelte, Angular, Solid, and Lit.

It is headless: the component computes the page-item model and exposes it through scoped slots, so you render each control however your design system wants, or accept the default, fully token-themed buttons. The current page is modelValue (the sole model: true prop, which drives an Angular ControlValueAccessor), so the pager binds to forms like any control.

Every visual value is a CSS custom property, so the pager re-skins to any design system, with ready-made bridges for shadcn/ui, Material 3, and Bootstrap 5.

The @rozie-ui/pagination packages

Pagination ships as six pre-compiled, per-framework packages. Install the one for your framework; there is no build step and no Rozie toolchain to set up:

PackageInstallREADME
@rozie-ui/pagination-reactnpm i @rozie-ui/pagination-reactreact/README
@rozie-ui/pagination-vuenpm i @rozie-ui/pagination-vuevue/README
@rozie-ui/pagination-sveltenpm i @rozie-ui/pagination-sveltesvelte/README
@rozie-ui/pagination-angularnpm i @rozie-ui/pagination-angularangular/README
@rozie-ui/pagination-solidnpm i @rozie-ui/pagination-solidsolid/README
@rozie-ui/pagination-litnpm i @rozie-ui/pagination-litlit/README

Each package carries only its framework peer (react + react-dom, vue, svelte, @angular/core + @angular/common + @angular/forms, solid-js, or lit + @lit-labs/preact-signals + @preact/signals-core).

Quick start

Two-way bind the page and supply either totalPages or total + pageSize. The @change event carries the new page:

vue
<script setup lang="ts">
import { ref } from 'vue';
import Pagination from '@rozie-ui/pagination-vue';

const page = ref(1);
</script>

<template>
  <Pagination v-model:modelValue="page" :total="195" :pageSize="10" @change="(e) => console.log(e.page)" />
</template>

The page-item model

From modelValue + the count props, the component derives a windowed list of page items — page numbers interleaved with 'ellipsis' markers — using the MUI usePagination semantics. siblingCount (default 1) widens the window around the current page; boundaryCount (default 1) sets how many pages are pinned at each end:

Current page (of 20)Rendered items (sibling 1, boundary 1)
11 2 3 4 5 … 20
101 … 9 10 11 … 20
201 … 16 17 18 19 20

The effective page count resolves from totalPages when given, else ceil(total / pageSize), and is always at least 1. A gap of exactly one page renders that single bridging page rather than a one-page ellipsis. This branchy logic lives in src/internal/paginationItems.ts and is unit-tested in isolation.

Headless rendering

Every control is a scoped slot — render your own markup and the component keeps the model, the bounds, the keyboard nav, and the ARIA wiring:

vue
<Pagination v-model:modelValue="page" :totalPages="20" :siblingCount="2">
  <template #item="{ page, selected, goto }">
    <button :aria-current="selected ? 'page' : undefined" @click="goto">{{ page }}</button>
  </template>
  <template #ellipsis>⋯</template>
  <template #prevControl="{ disabled, goto }">
    <button :disabled="disabled" @click="goto">Prev</button>
  </template>
  <template #nextControl="{ disabled, goto }">
    <button :disabled="disabled" @click="goto">Next</button>
  </template>
</Pagination>

See the full prop / event / slot / handle surface on the API reference, the live demo, and how it compares to existing libraries on the comparison page.

Pre-1.0 — APIs may change between minor versions.