Skip to content
TopClass UI

Pagination

Page numbers that are real links, not click handlers, so they work with no JavaScript and a crawler follows every page.

server - 0 KB

Pagination

server - 0 KB

Page numbers that are real links, not click handlers, so they work with no JavaScript and a crawler follows every page.

Install

Try it

value
pageCount
siblingCount
boundaryCount
size

The code

pagination.tsx
import { Pagination } from 'topclassui/pagination'

<Pagination value={6} pageCount={12} getHref={(page) => `/blog?page=${String(page)}`} />

getHref keeps every page crawlable; value, siblingCount and boundaryCount are clamped to a safe range.

Every state

4 states

Import from the package

topclassui/pagination
import { Pagination } from 'topclassui/pagination'

Install once:npm i topclassui@beta zod

Examples

  • Pass value, pageCount and getHref, which turns a page number into its address, such as /blog?page=3. Every cell is an anchor.
  • In Next.js a plain link reloads the page. Pass link={<Link href="" />} and each page change is a client navigation.
  • A bad page in the URL is clamped for what is shown: ?page=999 of 12 shows page 12. Query that same page with clampPage(Number(page), pageCount).
  • siblingCount and boundaryCount (0 to 3, default 1) set how many pages show around the current one and at each end.
  • size="md" is 40 px, the height of a Button; size="sm" is the 32 px cell of the design. custom emits no size classes.
  • The row wraps instead of scrolling. On a phone pass siblingCount={0} and boundaryCount={0} for a shorter row.
  • paginationVariants({ size, current }) returns the classes of one cell, for a link you render yourself.
middle
first-page
last-page
sizes
many-pages
two-navs

Orders 26 to 50 of 200

custom-size
wide-numbers
In Arabic, right to left

Props

The props of Pagination
PropTypeDefaultLimitsDescription
getHref*(page: number) => string--The address of a page, so every cell is a real link a crawler can follow.
pageCount*integer, min 1, max 9007199254740991-min 1, max 9007199254740991How many pages there are; a fraction is rounded up and anything below 1 is one page.
value*integer, min 1, max 9007199254740991-min 1, max 9007199254740991The current page, counted from 1; anything outside 1 to pageCount is clamped.
boundaryCountinteger, min 0, max 31min 0, max 3How many pages always show at each end of the list; clamped to 0-3.
labelsLabels--The built-in strings: the name of the nav, of each page and of the four icon links. Defaults to English.
linkReactElement--The element every link is cloned from, such as link={<Link href="" />}; its href is replaced.
localestring--The locale the page numbers are formatted in. Defaults to en.
siblingCountinteger, min 0, max 31min 0, max 3How many pages show on each side of the current one; clamped to 0-3.
sizexs | sm | md | lg | xl | custom"md"-The control scale each cell takes its height from; custom emits no size classes.

Keyboard and accessibility

The keyboard of Pagination
KeysWhat it does
TabMoves through first, previous, each page, next and last. A disabled control is skipped.
EnterFollows the focused link.
  • The current page carries aria-current="page" and is named "Page 3 of 12"; every other cell is named "Page 4".
  • Previous and next carry rel="prev" and rel="next". On the first and the last page that control is disabled and has no href.
  • Two navs on one page each need their own aria-label, such as "Orders pages, top" and "Orders pages, bottom".

Right to left

Under dir="rtl" the row runs from the right and the four carets flip. {...getLabels('ar')} passes the Arabic labels and locale together.

Server and client

Renders on the server and ships no JavaScript.

Files: index.tsx.

On the chip: server - 0 KB.