Pagination
Pagination is an interface that allows navigating between pages that contain split information, instead of being shown on a single page.
Features
- Full keyboard navigation.
- Optimized for screen readers.
Installation
To use the pagination machine in your project, run the following command in your command line:
npm install @zag-js/pagination @zag-js/react # or yarn add @zag-js/pagination @zag-js/react
npm install @zag-js/pagination @zag-js/solid # or yarn add @zag-js/pagination @zag-js/solid
npm install @zag-js/pagination @zag-js/vue # or yarn add @zag-js/pagination @zag-js/vue
npm install @zag-js/pagination @zag-js/vue # or yarn add @zag-js/pagination @zag-js/vue
This command will install the framework agnostic pagination logic and the reactive utilities for your framework of choice.
Anatomy
To set up the pagination correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Usage
First, import the pagination package into your project
import * as pagination from "@zag-js/pagination"
The pagination package exports two key functions:
machine
— The state machine logic for the pagination widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
id
to theuseMachine
hook. This is used to ensure that every part has a unique identifier.
import * as pagination from "@zag-js/pagination" import { useMachine, normalizeProps } from "@zag-js/react" import { data } from "./data" function Pagination() { const [state, send] = useMachine( pagination.machine({ id: "1", count: data.length }), ) const api = pagination.connect(state, send, normalizeProps) return ( <div> {api.totalPages > 1 && ( <nav {...api.rootProps}> <ul> <li> <a href="#previous" {...api.prevTriggerProps}> Previous <span className="visually-hidden">Page</span> </a> </li> {api.pages.map((page, i) => { if (page.type === "page") return ( <li key={page.value}> <a href={`#${page.value}`} {...api.getItemProps(page)}> {page.value} </a> </li> ) else return ( <li key={`ellipsis-${i}`}> <span {...api.getEllipsisProps({ index: i })}>…</span> </li> ) })} <li> <a href="#next" {...api.nextTriggerProps}> Next <span className="visually-hidden">Page</span> </a> </li> </ul> </nav> )} </div> ) }
import * as pagination from "@zag-js/pagination" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId, For } from "solid-js" import { data } from "./data" function Pagination() { const [state, send] = useMachine( pagination.machine({ id: createUniqueId(), count: data.length }), ) const api = createMemo(() => pagination.connect(state, send, normalizeProps)) return ( <div> {api().totalPages > 1 && ( <nav {...api().rootProps}> <ul> <li> <a href="#previous" {...api().prevTriggerProps}> Previous <span class="visually-hidden">Page</span> </a> </li> <For each={api().pages}> {(page, i) => { if (page.type === "page") return ( <li> <a href={`#${page.value}`} {...api().getItemProps(page)}> {page.value} </a> </li> ) return ( <li> <span {...api().getEllipsisProps({ index: i() })}> … </span> </li> ) }} </For> <li> <a href="#next" {...api().nextTriggerProps}> Next <span class="visually-hidden">Page</span> </a> </li> </ul> </nav> )} </div> ) }
import * as pagination from "@zag-js/pagination" import { normalizeProps, useMachine } from "@zag-js/vue" import { defineComponent, h, Fragment, computed } from "vue" import { data } from "./data" export default defineComponent({ name: "Pagination", setup() { const [state, send] = useMachine( pagination.machine({ id: "1", count: data.length }), ) const apiRef = computed(() => pagination.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div> {api.totalPages > 1 && ( <nav {...api.rootProps}> <ul> <li> <a href="#previous" {...api.prevTriggerProps}> Previous <span class="visually-hidden">Page</span> </a> </li> {api.pages.map((page, i) => { if (page.type === "page") return ( <li key={page.value}> <a href={`#${page.value}`} {...api.getItemProps(page)}> {page.value} </a> </li> ) else return ( <li key={`ellipsis-${i}`}> <span {...api.getEllipsisProps({ index: i })}> … </span> </li> ) })} <li> <a href="#next" {...api.nextTriggerProps}> Next <span class="visually-hidden">Page</span> </a> </li> </ul> </nav> )} </div> ) } }, })
<script setup> import * as pagination from "@zag-js/pagination" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" import { data } from "./data" const [state, send] = useMachine(pagination.machine({ id: "1", count: data.length })) const api = computed(() => pagination.connect(state.value, send, normalizeProps)) </script> <template> <nav v-if="api.totalPages > 1" v-bind="api.rootProps"> <ul> <li> <a href="#previous" v-bind="api.prevTriggerProps"> Previous <span class="visually-hidden">Page</span> </a> </li> <li v-for="(page, i) in api.pages" :key="page.type === 'page' ? page.value : `ellipsis-${i}`" > <span v-if="page.type === 'page'"> <a :href="`#${page.value}`" v-bind="api.getItemProps(page)"> {{page.value}} </a></span > <span v-else> <span v-bind="api.getEllipsisProps({ index: i })">…</span> </span> </li> <li> <a href="#next" v-bind="api.nextTriggerProps"> Next <span class="visually-hidden">Page</span> </a> </li> </ul> </nav> </template>
const api = connect(state, send) // You can slice the data, to get data for current page const currentPageData = data.slice(api.pageRange.start, api.pageRange.end) api.page // => 1 api.isFirstPage() // => true api.setPage(3) // page => 3 api.previousPage // => 2 api.nextPage // => 4 api.pages /* [ { "type": "page", "value": 1, }, { "type": "page", "value": 2, }, { "type": "ellipsis", }, { "type": "page", "value": 3, }, { "type": "page", "value": 4, }, ] */ api.pageRange // => { start: 4, end: 13 }
Styling guide
Earlier, we mentioned that each pagination part has a data-part
attribute
added to them to select and style them in the DOM.
[data-part="root"] { /* styles for the pagination's root */ } [data-part="item"] { /* styles for the pagination's items */ } [data-part="ellipsis"] { /* styles for the pagination's ellipsis */ } [data-part="prev-trigger"] { /* styles for the pagination's previous page trigger */ } [data-part="next-trigger"] { /* styles for the pagination's next page trigger */ } /* We add a data-disabled attribute to the prev/next items when on the first/last page */ [data-part="prev-trigger"][data-disabled] { /* styles for the pagination's previous page trigger when on first page */ } [data-part="next-trigger"][data-disabled] { /* styles for the pagination's next page trigger when on first page */ }
Methods and Properties
Machine Context
The pagination machine exposes the following context properties:
ids
Partial<{ root: string; ellipsis(index: number): string; prevTrigger: string; nextTrigger: string; item(page: number): string; }>
The ids of the elements in the accordion. Useful for composition.translations
IntlTranslations
Specifies the localized strings that identifies the accessibility elements and their statescount
number
Total number of data itemspageSize
number
Number of data items per pagesiblingCount
number
Number of pages to show beside active pagepage
number
The active pageonPageChange
(details: PageChangeDetails) => void
Called when the page number is changed, and it takes the resulting page number argumenttype
"button" | "link"
The type of the trigger elementdir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The pagination api
exposes the following methods:
page
number
The current page.totalPages
number
The total number of pages.pages
Pages
The page range. Represented as an array of page numbers (including ellipsis)previousPage
number
The previous page.nextPage
number
The next page.pageRange
PageRange
The page range. Represented as an object with `start` and `end` properties.slice
<V>(data: V[]) => V[]
Function to slice an array of data based on the current page.isFirstPage
boolean
Whether the current page is the first page.isLastPage
boolean
Whether the current page is the last page.setCount
(count: number) => void
Function to set the total number of pages.setPageSize
(size: number) => void
Function to set the page size.setPage
(page: number) => void
Function to set the current page.
Edit this page on GitHub