Pagination

Move between pages of results. The windowing rule — how many neighbours to show and where the gaps fall — is exported as pageItems, so it can be reused or tested on its own. OffsetPagination and CursorPagination cover lists that cannot be browsed by page number: a range, Previous and Next.

src/components/ui/pagination.tsx

About

From the component's own source, where it was written.

Move between pages of results.

pageItems is exported because the windowing rule — how many neighbours to show, and where gaps fall — is the part worth testing and reusing, not the markup around it.

Install

npx astralyx-ui add pagination

Usage

import { Pagination } from '@/components/ui/pagination'

const [page, setPage] = useState(1)

<Pagination page={page} count={20} onPageChange={setPage} />

Composer

Change the props and the source below follows.

Props
tsx
<Pagination
  page={page}
  count={20}
  onPageChange={setPage}
/>

Examples

Default

Few pages — no gaps

More siblings

Compact

By offset

By cursor, with a capped total

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Pagination

Extends Omit<ComponentProps<'nav'>, 'onChange'>.

PropTypeDefaultDescription
pagenumberrequiredCurrent page, 1-based. Controlled — the component holds no state.
countnumberrequiredTotal number of pages.
onPageChange(page: number) => void—Fires with the requested page. Out-of-range requests are filtered out first.
siblingsnumber1Neighbours shown either side of the current page. First and last are always present.
sizeButtonProps['size']'icon-sm'Button size for the controls.
compactbooleanfalseDrop the numbers and show "page / count" between the arrows.
labelstring'Pagination'Accessible name for the nav landmark.
previousLabelstring'Previous page'—
nextLabelstring'Next page'—
pageLabel(page: number) => stringDEFAULT_PAGE_LABELAccessible name for a page button.

OffsetPagination

"21–40 of 57" with Previous and Next, for a list paged by offset and pageSize. Renders nothing while everything fits on one page, and when the total shrinks under the current page — rows deleted, a stale link — moves to the last page that still holds rows.

Extends Omit<ComponentProps<'nav'>, 'onChange'>.

PropTypeDefaultDescription
pageSizenumberrequiredRows per page.
sizeButtonProps['size']'icon-sm'Button size for the controls.
labelstring—Accessible name for the nav landmark.
previousLabelReactNode——
nextLabelReactNode——
rangeLabel(range: RangeParts) => ReactNode—The range line, from numbers already formatted for locale. Defaults to "21–40 of 57".
unknownLabelReactNode—Shown in place of the range while the total is still loading.
localestring—Formats the numbers in the range line.
offsetnumberrequired—
totalnumberrequiredUndefined while it loads.
onOffsetChange(offset: number) => voidrequired—
maxOffsetnumber—The deepest offset the API accepts. Next stops there, and an offset past it is moved back.

CursorPagination

The same line for a keyset-paged list, which knows only its page index, the rows on it and the next cursor. totalCapped shows a total counted up to a cap as "10,000+".

Extends Omit<ComponentProps<'nav'>, 'onChange'>.

PropTypeDefaultDescription
pageSizenumberrequiredRows per page.
sizeButtonProps['size']'icon-sm'Button size for the controls.
labelstring—Accessible name for the nav landmark.
previousLabelReactNode——
nextLabelReactNode——
rangeLabel(range: RangeParts) => ReactNode—The range line, from numbers already formatted for locale. Defaults to "21–40 of 57".
unknownLabelReactNode—Shown in place of the range while the total is still loading.
localestring—Formats the numbers in the range line.
pagenumberrequiredCurrent page, 1-based. Controlled — the component holds no state.
rowsnumberrequiredRows on the page shown. Undefined while it loads.
totalnumberrequired—
totalCappedbooleanfalseThe total was counted only up to a cap — shown as "10,000+".
nextCursorstringrequiredThe cursor of the page after this one. None means this is the last.
onNext(cursor: string) => voidrequired—
onPrevious() => voidrequired—

Hooks

HookSignatureDescription
useCursorPagination(resetKey: string) => { cursor: string; page: number; next: (cursor: string) => void; previous: () => void; first: () => void; }The trail of cursors behind the current page — a cursor API has no way back, so it is remembered here. A new resetKey (the filters changed) starts again at the first page.

Functions

FunctionSignatureDescription
pageItems(page: number, count: number, siblings = 1) => PageItem[]The exported windowing function behind the row.
lastPageOffset(total: number, pageSize: number) => numberThe offset of the last page holding anything.

Types

TypeDefinitionDescription
PageItem
number | 'gap'
Move between pages of results.
RangePaginationProps
Omit<ComponentProps<'nav'>, 'onChange'> & {
  /** Rows per page. */
  pageSize: number
  size?: ButtonProps['size']
  /** Accessible name for the nav landmark. */
  label?: string
  previousLabel?: ReactNode
  nextLabel?: ReactNode
  /** The range line, from numbers already formatted for `locale`. */
  rangeLabel?: (range: RangeParts) => ReactNode
  /** Shown in place of the range while the total is still loading. */
  unknownLabel?: ReactNode
  /** Formats the numbers in the range line. */
  locale?: string
}
—