Quire UI
Navigation3

Pagination

A pager that never changes width, with a sliding marker, arrow keys, and a compact jump-to-page form.

31–40 of 1,284 invoices

Installation

npx shadcn@latest add @quireui/pagination

Usage

<Pagination page={page} pageCount={129} onPageChange={setPage} />

A pager that stays put

The pager always shows the same seven slots: the first and last page, the current page with one on each side, and a gap wherever pages are skipped. Every slot is as wide as the longest page number. So the pager never changes width while you page, and the next button never moves away from your cursor.

The marker slides to the current page. Paging through the middle, it stays in the centre and the numbers change around it.

With onPageChange the pages are buttons, for paging in place. Add getHref and they become real links, so a page can be opened in a new tab, shared or crawled; with both, a plain click still pages in place.

<Pagination page={page} pageCount={20} getHref={(p) => `?page=${p}`} onPageChange={setPage} />

Loading

When the next page comes from a server, pass loading while it is on its way. The marker has already moved, so the click feels answered; a thin line runs inside it if loading takes longer than 150ms, so fast pages never flicker. With a transition it costs nothing:

const [isPending, startTransition] = useTransition();

<Pagination
  page={page}
  pageCount={pageCount}
  onPageChange={(next) => startTransition(() => setPage(next))}
  loading={isPending}
/>

In the first example, the old rows stay, dimmed, until the new page arrives. That is calmer than flashing skeletons for a page that loads in half a second; keep Skeleton for the first load.

Compact

variant="compact" shows "Page 37 of 129" with a field to type a page into, for long lists where the numbers would tell you little. Arrow up and down in the field step a page.

Summary and page size

PaginationSummary says where the visible rows sit, like "31–40 of 1,284 invoices", and is announced politely when it changes. PaginationPageSize is a small select for rows per page. In this example, changing the page size keeps the first visible row in view instead of jumping back to page one.

31–40 of 1,284 invoices

Accessibility

The pager is a nav landmark named "Pagination". The current page is marked aria-current="page", every page button says which page it opens, and the pager is marked busy while loading. PaginationSummary is announced politely when it changes.

KeyAction
← / →With focus in the pager, goes to the previous or next page. Focus follows the current page.
Home / EndGoes to the first or last page.
EnterIn the compact field, goes to the typed page.
↑ / ↓In the compact field, steps one page.

Motion

The marker slides in 160ms. From the keyboard it moves instantly, so arrowing through pages never waits on an animation.

API reference

Pagination

Prop

Type

Also takes every <nav> attribute. Style it with [data-slot="pagination"].

PaginationSummary

"31–40 of 1,284 invoices": where the visible rows sit in the whole set.

Prop

Type

Also takes every <p> attribute. Style it with [data-slot="pagination-summary"].

PaginationPageSize

How many rows per page.

Prop

Type

On this page