Pager
import { Pager } from '@tangible/ui';
Interactive docs and examples: Pager in Storybook.
Pager props
| Prop | Type | Default | Description |
|---|---|---|---|
currentPage |
number |
Current page (1-indexed) | |
onPageChange |
(page: number) => void |
Called when user selects a different page | |
totalPages |
number |
Total number of pages | |
className? |
string |
Additional class name | |
hidden? |
boolean |
false |
Hide the entire pager (useful when only 1 page) |
labels? |
PagerLabels |
Overridable label strings for i18n. All keys are optional — defaults are English. | |
maxNumbers? |
number |
7 |
Max page number buttons in smart mode |
mode? |
"ends" | "full" | "simple" | "smart" |
'smart' |
Pagination mode: 'simple' (prev/next only), 'ends', 'full', 'smart' |
navStyle? |
"icon" | "text" |
'text' |
Style for prev/next: 'text' shows labels, 'icon' shows chevrons |
onPageSizeChange? |
(size: number) => void |
Called when user changes page size | |
pageSize? |
number |
Current page size (for page size selector) | |
pageSizeOptions? |
number[] |
Available page size options | |
showFirstLast? |
boolean |
false |
Add dedicated first-page / last-page buttons outside prev/next. Most valuable in simple mode and at high page counts — in smart, ends and full the numbered group already carries a "1" and a last page, so these mainly save the user having to find them. Opt-in for that reason: four nav buttons is a lot of chrome to add by default. In flush they join the connected group at the ends, which means prev and next stop being the end segments — the border-collapse rules key off actual position rather than kind, so this needs no special casing. |
showInfo? |
boolean |
true |
Show the "Page X of Y" region. The page size selector has its own condition (pageSizeOptions + onPageSizeChange), so this governs the region, not the select. Defaults to true in every mode — including simple, where the status line is arguably redundant. Auto-hiding there is what most consumers probably want, but it would silently change layout for existing simple users, so it stays opt-out. |
showJump? |
boolean |
false |
Add a "Go to page" field. For hundreds of pages, where the numbered buttons and even first/last are still slow. Commits on Enter or via its own submit button — deliberately not on blur, which would move the page out from under a user who tabbed away mid-edit. Out-of-range entries clamp to the nearest valid page rather than erroring, and the existing role="status" region announces where the jump actually landed. It renders outside the <nav> landmark, which is the connected button group itself — so someone arriving by landmark finds prev/next and the numbers, not this field. It follows immediately in tab order. Moving it inside would mean wrapping the group rather than being it, and the field would then have to sit beside the buttons instead of at the trailing edge. |
size? |
"lg" | "md" | "sm" |
'sm' |
Control size — joins the shared control-height ladder (sm/md/lg) that Button, IconButton, and the form controls use. |
variant? |
"detached" | "flush" |
'flush' |
Whether nav segments are joined into one group or spaced apart. |
Other exports
defaultPagerLabels:Required<PagerLabels>
Examples
-
Simple Mode: Simple mode shows only Prev/Next buttons without page numbers.
-
Full Mode: Full mode shows all page numbers. Best for small page counts.
-
With Page Size Selector: With page size selector for adjustable items per page.
-
Many Pages: Smart mode handles large page counts with ellipsis.
-
Few Pages: With only a few pages, all numbers are shown.
-
Single Page: Single page with both nav buttons disabled.
-
Ends Mode: Ends mode always keeps the first and last page visible, with ellipsis collapsing the middle. Compare with smart mode, which windows around the current page.
-
Localised Labels: Every announced string routes through
labels— nav landmark name, button labels, and therole="status"live region that announces page changes. WCAG 2.5.3 Label in Name: the visible button text (previousText/nextText) must be contained in the accessible name (previous/next), or voice-control users can't activate the buttons by saying what they see — keep that containment when translating. -
Detached (text nav):
variant="detached"spaces the segments apart instead of joining them into a connected group — the pill-style pagination common on front-ends, as opposed to the admin-table lookflushgives.Prev/next carry no border or fill in this variant: they sit outside the group as ghost affordances rather than outline segments completing it.
-
Detached (icon nav): The same variant with
navStyle="icon"— prev/next reduce to bare chevrons. -
Variants compared: Both variants together, for comparison. The gap is the only structural difference in the number group —
--tui-pager-nav-gapdrives it, and the flush variant zeroes it.Note the distinct
labels.navigationon each. Every Pager renders a<nav>landmark named "Pagination" by default, so two on one page are indistinguishable to a screen reader moving by landmark (axe flags it aslandmark-unique). Any page with more than one Pager — the common case being one above and one below a long table — has to differentiate them. -
Simple mode, no info:
showInfo={false}drops the "Page X of Y" region. Most useful insimplemode, where the status line repeats context the prev/next pair already implies — which is why it always rendered there and could not be removed.It governs the region, not the page size selector: that has its own condition (
pageSizeOptions+onPageSizeChange). -
First / last (flush):
showFirstLastadds dedicated first/last buttons outside prev/next, so the order reads « ‹ 1 2 3 › ». They are nav affordances rather than page numbers, so they take the same treatment as prev/next in both variants.In
flushthey join the connected group at the ends — which means prev and next are no longer the end segments. The border-collapse rules key off actual position rather than which kind of button it is, so the outer radius lands on the new ends without any special casing. -
First / last (icon nav): With
navStyle="icon", first/last are double chevrons —system/chevron-left-doubleandsystem/chevron-right-double, added for this affordance. The doubling is what distinguishes them from prev/next at a glance. -
First / last (simple mode): Where first/last earn their keep most:
simplemode has no numbered group at all, so without them the only way back to page 1 is to click Prev repeatedly. -
First / last (detached): Detached, for the ghost treatment on all four nav buttons at once.
-
Go to page:
showJumpadds a go-to-page field for page counts where even first/last are slow. It commits on Enter or via its own submit button — not on blur, which would move the page out from under someone who tabbed away mid-edit.Out-of-range entries clamp rather than error: typing
999here goes to page 40, and the Pager's existingrole="status"region announces where the jump actually landed, so the correction is audible rather than silent. The field clears on commit — "Page X of Y" is what displays state, so the input never sits there disagreeing with a page reached some other way. -
Everything on: Everything at once: first/last, the numbered group, the info region, a page size selector and the jump field — the densest the Pager gets, and a check that the row still lays out sensibly when it wraps.
-
Go to page, no info: The jump field with no info region —
showInfo={false}removes "Page X of Y" and the page size selector, and the jump still sits at the trailing end. The nav owns the auto margin that pushes it there, so it does not matter which of the trailing regions render. -
Responsive (container query): The Pager is a container query container (
container-name: tuiPager), so it responds to the width it is given rather than to the viewport — the same approach Card and Notice use.Two things happen as the space narrows:
- The nav wraps. Unconditionally, not below a breakpoint: the nav's width
depends on
mode,maxNumbers,showFirstLastandsize, so no single threshold can promise it fits —mode="full"at 30 pages is wider than any sane breakpoint. Wrapping costs nothing while it fits, and the alternative is the nav pushing the page into horizontal scroll (WCAG 2.1 SC 1.4.10). - Below
32rem,flushadopts the detached presentation. A connected group cannot wrap and still look like one: the collapse rules key off:first-child/:last-child, and CSS cannot identify the first item of a wrapped row, so a second row would open with a borderless, square-cornered box. Pills wrap correctly by construction.
32remis a design line rather than a derived number, so a narrow pager degrades slightly before it strictly has to. The rules sit at the same specificity as the flush block, so a consumer can override the call. - The nav wraps. Unconditionally, not below a breakpoint: the nav's width
depends on
Other stories: Default, Icon Navigation, Sizes (interactive page).
Component API tokens
Read via fallback, never declared. Set one on an ancestor to restyle every instance under it.
--tui-pager-gap--tui-pager-nav-gap--tui-pager-item-border--tui-pager-item-radius--tui-pager-jump-width