Cursor Pager
Previous and Next for keyset paging — a range label and two buttons, no page numbers and no total.
- Status
- Since
0.25.69- Accessibility pattern
- navigation landmark, aria-disabled ends, live range
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Cursor Pager before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify cursor-pagerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/cursor-pagerRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/use-announcer.
Usage
import { CursorPager } from "@/components/ui/cursor-pager";
const [history, setHistory] = React.useState<(string | null)[]>([]);
const [cursor, setCursor] = React.useState<string | null>(null);
<CursorPager
label={`Rows ${history.length * 100 + 1}–${history.length * 100 + page.rows.length}`}
hasPrevious={history.length > 0}
hasNext={page.nextCursor != null}
onPrevious={() => {
setCursor(history.at(-1) ?? null);
setHistory(history.slice(0, -1));
}}
onNext={() => {
setHistory([...history, cursor]);
setCursor(page.nextCursor);
}}
/>;A keyset-paged list knows whether a page exists on either side and what the current page holds,
but not how many pages there are or where page 7 starts. CursorPager shows exactly that: a range
label at the leading edge, and Previous and Next. It is controlled and owns no cursors — the
host keeps the cursor history (the stack of cursors it came through) and fetches.
DataGrid's pagedMode renders it under the grid, for people who
page instead of scroll. For a list with a total and page numbers, reach for
DataListPager.
Examples
At the first page, and while a page loads
At either end the button stays in the tab order with aria-disabled; while loading both hold
still and the pager is aria-busy.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
hasNext* | boolean | — | A page exists after this one. |
hasPrevious* | boolean | — | A page exists before this one. |
onNext* | () => void | — | Go to the next page (the host pushes the current cursor). |
onPrevious* | () => void | — | Go to the previous page (the host pops its cursor history). |
label | React.ReactNode | — | What the current page holds, shown at the leading edge and announced when it changes — for example "Rows 101–200". |
loading | boolean | false | A page is loading: both buttons hold still until it lands. |
nextLabel | string | "Next" | The Next button's text. |
previousLabel | string | "Previous" | The Previous button's text. |
Data attributes and CSS variables on CursorPager
| Attribute | Values |
|---|---|
data-slot | "cursor-pager" | "cursor-pager-label" | "cursor-pager-next" | "cursor-pager-previous" |
Accessibility
- A
navigationlandmark named Pagination (aria-labelrenames it). - Previous and Next are buttons that stay focusable at either end (
aria-disabled), rather than disappearing. - A new
labelstring is announced politely through one live region; the label on screen at first render is not.
| Key | Action |
|---|---|
| Tab | Move to Previous, then Next. |
| Enter / Space | Go to that page. |
| Contract | States tested |
|---|---|
| Behaviour | default, first-page, last-page, loading |
| Accessibility | keyboard, labeled, status-announcement |
| Visual | default, disabled |