Usage

When a list needs page numbers instead of a scroll, what the server has to send, and which flags to set yourself.

When to use

Pagination is for a list the reader is searching. It says how many items there are, which slice is showing, and lets the reader reach any slice (including the last one) in one move.
  • The reader is looking for a known item, or wants to come back to where they were. “Page 7” is only useful if it is still page 7 tomorrow.
  • The order is stable: an id, a name, a created date that does not move. A page boundary only means something over a stable sort.
  • The total is worth showing. A result count is often the answer by itself, and showTotal is the cheapest place to put it.
  • The rows are dense and uniform: a table, an admin list, search results.
Use something else when:
  • The reader is browsing rather than hunting. Infinite scroll, or a Load more button, keeps them in the flow and costs them no decision. A feed has no page 7 that anyone wants to bookmark.
  • The list is long but is meant to read as one continuous run. Virtual scrolling keeps one scrollbar, one scroll position and one set of keyboard behavior. Pagination cuts the same run into arbitrary slices.
  • The order moves under the reader (recency, a live query, a relevance score that is recomputed). Boundaries move with it, so one item can appear on page 2 and page 3 in the same session, or on neither.
  • The API is cursor-paged. A cursor cannot answer “how many”, and Pagination needs that answer. Load more is the control that matches that API.
  • There is only ever one page. hideOnSinglePage covers the case, and a control that never does anything is noise.

Where total comes from

Pagination computes the page count as Math.ceil(total / pageSize), and that one number decides how many page items render, whether the jumpers appear, whether prev and next are disabled and, by default, whether the size changer exists at all. It defaults to 0, so a Pagination with total left out renders one disabled item and nothing else.
  • total counts items. Passing 20 because there are twenty pages gives you two.
  • The server has to supply it. A page of rows does not know how many pages there are, so return the count with the rows.
  • If the count is expensive to compute, use a different control instead of guessing. An approximate total makes the last page wrong, and the last page is the one readers check.
  • It has to agree with the list underneath. A total of 240 beside a query that runs out at page 8 shows empty pages, and nothing warns about it.
  • It changes when a filter changes. Refetch the count with the rows and reset current to 1 in the same update, or the reader lands on page 9 of a two-page result.

current and pageSize

Uncontrolled (defaultCurrent, which is 1, and defaultPageSize, which is 10) is only right when nothing else on the page reads the page number. Usually something does: the fetch does, and often the URL does too.
  • Pass current and you must pass onChange. The component warns in development (“You provided a current prop to a Pagination component without an onChange handler. This will render a read-only component”), and the control never moves.
  • onChange is (page, pageSize). Either can be what changed, so refetch from the pair, not from page plus your remembered size.
  • A size change fires both callbacks, onShowSizeChange(current, size) first, then onChange(nextCurrent, size). Refetch in onChange only, or one interaction costs two requests.
  • A size change also clamps. The component recomputes the last page and moves current down to it, so going from 10 to 100 per page while on page 9 lands on page 1. Write the page number from onChange's argument, never from your own arithmetic.
  • current is clamped on render too, to Math.max(1, Math.min(current, pageCount)), so an out-of-range page paints as the last page while your state still holds the bad value. Keep the two in step.
  • Put the page in the URL when the list is worth linking to. That is the main reason to choose pagination over a Load more button.

The size changer

The size changer turns itself on past 50 items. Left unset, showSizeChanger is on whenever total is over totalBoundaryShowSizeChanger, which is 50. Pass showSizeChanger={false} where you do not want one.
  • The options default to [10, 20, 50, 100]. Pass pageSizeOptions for your own and keep the steps predictable: a reader picks the next one up, not a specific number.
  • The current pageSize is spliced into that list and sorted in if it is not already one of the options, so a size of 25 against the defaults renders 10, 20, 25, 50, 100. Either add your size to the options or expect to see it there.
  • Keep the top option affordable. An option that asks the server for 500 rows is one somebody will pick, and then keep.
  • showSizeChanger also takes a SelectProps object, which turns the control on and configures it. The kit uses it to give the changer your icon pack's search icon, only on lists where the changer would show anyway.
  • To turn the size changer off for every Pagination in a subtree, set showSizeChanger: false or a higher totalBoundaryShowSizeChanger in ConfigProvider's pagination config. A prop on the Pagination itself wins over it.
  • The changer is a Select, so it brings a dropdown, a search box and a popup layer with it. On a surface that cannot afford those, drop the changer and pick one size.

The quick jumper

  • showQuickJumper earns its place past about ten pages, where the reader can no longer reach an arbitrary page in one click. Under that it is a text input competing with page numbers that already work.
  • It only renders when total > pageSize, so it disappears on its own once a filter shrinks the list.
  • The input commits on Enter and on blur. showQuickJumper={{ goButton: true }} adds a confirm button, which helps on touch screens, where nobody blurs a field to submit it.
  • An out-of-range number is clamped to the last page and a non-numeric one is discarded, so there is no error state to design. It gives no confirmation either, so announce the change yourself, as the Accessibility tab describes.

What showTotal says

showTotal is (total, range) => node, where range is the [from, to] of the slice on screen. Use both halves: “1 to 10 of 240 orders” answers where am I and how much is there in one line.
  • Name the items. “240 orders” says what is counted, and “240” leaves the reader to guess.
  • It renders first in the row, before prev.
  • Do not restate the count above the list as well. One count per list, or the two will eventually disagree in front of somebody.
  • Keep it to one line. A long sentence pushes the page numbers around at narrow widths.

simple, align, responsive

  • simple collapses the whole control to prev, an editable 3 / 20, next. It belongs in a drawer, a card footer or a phone-width list: anywhere the row cannot afford numbers. The cost is that the reader can no longer see or click a specific page.
  • simple={{ readOnly: true }} keeps the same row and drops the input. Use it when the reader cannot jump to a page.
  • showLessItems sits between full and simple. It shows one page on each side of the current one instead of two, and its jumpers step 3 pages instead of 5.
  • align takes start, center and end, and has no default. Pick one per surface and keep it. A list's pagination usually sits at the same end as the list's own actions, and a centered one reads as a footer for the page.
  • responsive switches to the small size below 576px. It makes the row denser, and a full pagination in a narrow column still wraps. At phone width, prefer simple.
  • size sets the same density directly, and follows ConfigProvider's size when you leave it out. There is no variant or color prop. The current page takes your primary color.

Icons

  • Your own icons win. Pass a node to prevIcon, nextIcon, jumpPrevIcon or jumpNextIcon to replace the kit's, or null to draw none. A nulled arrow leaves an empty button, so give that item text or an aria-label through itemRender.
  • Your own icons do not mirror in RTL. The kit's chevrons flip under dir="rtl". An icon you pass yourself does not, so add rtl:-scale-x-100 to it.

No ref

There is no ref. Pagination does not forward one. Wrap it in a <nav aria-label="Pagination"> when you need an element to hold on to, which also names the control for screen readers.

In a Table

  • A Table renders its own Pagination and feeds it from its own state. Configure it through the pagination prop, which takes every prop on this page plus position (an array of topLeft to bottomRight, or none). Do not render a second Pagination beside it.
  • pagination={false} turns it off, which is what you want when the table sits inside something that already pages, or when the rows are virtualized.
  • Server-side paging goes through the Table's own onChange(pagination, filters, sorter), not through a Pagination handler. The Table combines page, filter and sort into one change, so fetch from there.
  • A standalone Pagination is for a list that is not a Table: cards, a media grid, search results. There the state is yours, and so is deciding what happens to scroll position and to focus on a page change.