PaginationBar
Moves through a long list a page at a time.
SourcePackage @weft-js/warpPlayground
Preview
tsx
import { state } from '@weft-js/core'import { PaginationBar } from '@weft-js/warp'export function Offset() { const page = state(6) const size = state(25) return <PaginationBar label="Deploys" page={page} size={size} total={2480} />}Installation
npm install @weft-js/warpUsage
tsx
import { PaginationBar } from '@weft-js/warp'Previous and Next
mode="cursor" is for a list with no page count. You move pageIndex.
tsx
import { state } from '@weft-js/core'import { PaginationBar } from '@weft-js/warp'export function Cursor() { const index = state(0) return ( <PaginationBar label="Audit log" mode="cursor" size={50} pageIndex={index.value} hasPrev={index.value > 0} hasMore={index.value < 3} onPageChange={i => (index.value = i)} /> )}In a small space
Under 520px of its own width only the arrows and the current page stay.
tsx
import { state } from '@weft-js/core'import { PaginationBar } from '@weft-js/warp'export function Narrow() { const page = state(4) const size = state(25) return <PaginationBar label="Deploys" page={page} size={size} total={2480} />}Moved from outside
Pass a state as page and write to it from anywhere.
tsx
import { state } from '@weft-js/core'import { Button, PaginationBar, Row, Stack, Text } from '@weft-js/warp'export function Controlled() { const page = state(8) const size = state(10) return ( <Stack gap="related"> <PaginationBar label="Projects" page={page} size={size} total={240} /> <Row gap="related"> <Button size="sm" emphasis="outline" tone="neutral" onClick={() => (page.value = 1)}>Back to the start</Button> <Text subtle>Page {page.value}, set from outside</Text> </Row> </Stack> )}Paging a list
Pass page, size, sizes and total to move through a long list a few rows at a time.
tsx
import { state } from '@weft-js/core'import { Badge, PaginationBar, Row, Stack, Text } from '@weft-js/warp'export function List() { const page = state(1) const size = state(4) const rows = () => PROJECTS.slice((page.value - 1) * size.value, page.value * size.value) return ( <Stack gap="group"> <Stack gap="related" divided> {rows().map(p => ( <Row key={p.id} justify="between" gap="group"> <Stack gap="hair"><Text>{p.name}</Text><Text role="caption" subtle>{p.owner}, {p.region}</Text></Stack> <Badge tone={TONE_OF[p.status]} dot>{p.status}</Badge> </Row> ))} </Stack> <PaginationBar label="Projects" page={page} size={size} sizes={[4, 6, 10]} total={PROJECTS.length} /> </Stack> )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [ { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' }, { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' }, { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' }, { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' }, { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' }, { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' }, { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' }, { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' }, { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' }, { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const TONE_OF: Record<Status, 'positive' | 'info' | 'critical' | 'neutral'> = { Live: 'positive', Building: 'info', Failed: 'critical', Paused: 'neutral' }When to use
Use it for
- A list people jump around in, or link to a page of
- You know the total, or at least whether there is more
Reach for something else
- LoadMore, when people read straight down a feed
- VirtualList, when it is one long list that scrolls
Playground
Total
Size
import { PaginationBar } from '@weft-js/warp'<PaginationBar label="Projects" page={page} total={1240} />Accessibility
- Tab
- Moves between the page buttons
- Enter
- Goes to that page, or the number typed in Go to
- After a press, focus moves to the new current page, so it is never lost when a button goes away.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | 'Pagination' | The name of the navigation landmark. |
mode | "cursor" | "offset" | 'offset' | offset (default): numbered pages. cursor: Previous and Next, for a list with no page count. |
page | number | State<number> | — | 1-based. |
defaultPage | number | — | The page it opens on when page isn't given. |
total | number | 0 | How many rows there are, in numbered mode. |
size | number | State<number> | — | Rows per page. Pass a state so the rows-per-page select can change it. |
sizes | number[] | [10, 25, 50, 100] | What the rows-per-page select offers. |
hasMore | boolean | false | Cursor mode: enables Next. |
hasPrev | boolean | false | Cursor mode: enables Previous. |
pageIndex | number | 0 | Cursor mode: which page this is, from 0. |
supply | number | — | Cursor mode: the total, when you happen to know it. |
totalFetched | number | — | Cursor mode: how many rows are showing, when pages differ in size. |
locale | string | — | The locale its numbers are written in. en-US without it. |
| Event | Type | Default | Description |
|---|---|---|---|
onPageChange | ((page: number) => void) | — | Hears the new page (offset) or page index (cursor) on every move. |
onSizeChange | ((size: number) => void) | — | Hears a new rows-per-page choice; the bar goes back to page 1. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="pagination"] [data-part="spacer"]
spacerfieldlongpagesr-onlyjump