Pagination
SourceControls for moving through a paged dataset. Pagination.Root owns the controlled page model and shares it with its parts: a navigation ButtonGroup of first/previous/next/last buttons, an editable page field, a "Showing X–Y of Z" status line, and a page-size picker. It composes the shipped Button, ButtonGroup, Input, and Select — so it inherits their sizing, theming, and accessibility.
Basic
Pass page and onPageChange (the controlled model), plus total and perPage so the status line and the last-page bound can be computed. Compose Pagination.Previous and Pagination.Next inside Pagination.Controls for the smallest useful control; the buttons disable themselves at the bounds.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
export default function PaginationBasic() {
const [page, setPage] = useState(1);
return (
<Pagination.Root page={page} onPageChange={setPage} total={100} perPage={10}>
<Pagination.Info />
<Pagination.Controls>
<Pagination.Previous />
<Pagination.Next />
</Pagination.Controls>
</Pagination.Root>
);
}
Full controls
Add Pagination.First, Pagination.Last, and the editable Pagination.Page for the full set. The buttons weld into one ButtonGroup; the page field accepts a typed page (Enter or blur commits, clamped to the valid range).
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
export default function PaginationFull() {
const [page, setPage] = useState(3);
return (
<Pagination.Root page={page} onPageChange={setPage} total={250} perPage={25}>
<Pagination.Info />
<Pagination.Controls>
<Pagination.First />
<Pagination.Previous />
<Pagination.Page />
<Pagination.Next />
<Pagination.Last />
</Pagination.Controls>
</Pagination.Root>
);
}
Simple
When you only know the page count (not the item total), pass pageCount directly and use just the previous/next buttons. size="sm" gives a denser control.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
export default function PaginationSimple() {
const [page, setPage] = useState(1);
return (
<Pagination.Root page={page} onPageChange={setPage} pageCount={8} size="sm">
<Pagination.Controls>
<Pagination.Previous />
<Pagination.Next />
</Pagination.Controls>
</Pagination.Root>
);
}
Page size
The page-size picker is composable: Pagination.PageSize is a layout cluster holding a separate Pagination.PageSizeLabel (the "Per page" text — override it with children) and Pagination.PageSizeSelect (the Select, controlled via value + onValueChange). Keep them apart so you can reorder, restyle, translate, or drop either. Reset the page to 1 in the handler so the new size starts from the top; Pagination.Separator draws a quiet divider between clusters.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
export default function PaginationPageSize() {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(25);
return (
<Pagination.Root page={page} onPageChange={setPage} total={500} perPage={perPage}>
<Pagination.Info />
<Pagination.PageSize>
<Pagination.PageSizeLabel />
<Pagination.PageSizeSelect
value={perPage}
onValueChange={(size) => {
setPerPage(size);
setPage(1);
}}
/>
</Pagination.PageSize>
<Pagination.Separator />
<Pagination.Controls>
<Pagination.First />
<Pagination.Previous />
<Pagination.Next />
<Pagination.Last />
</Pagination.Controls>
</Pagination.Root>
);
}
Sizes
size on Pagination.Root (sm or md, the default) scales the hosted controls. The nav buttons and the welded page field both drop to sm (the field's height is pinned to match the buttons); the page-size select uses its md minimum, since Select has no smaller rung.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
const SIZES: Pagination.Size[] = ["sm", "md"];
export default function PaginationSizes() {
const [page, setPage] = useState(2);
return (
<div className="flex flex-col items-start gap-4">
{SIZES.map((size) => (
<Pagination.Root key={size} page={page} onPageChange={setPage} pageCount={5} size={size}>
<Pagination.Controls>
<Pagination.First />
<Pagination.Previous />
<Pagination.Page />
<Pagination.Next />
<Pagination.Last />
</Pagination.Controls>
</Pagination.Root>
))}
</div>
);
}
Tooltips
Wrap each nav button in a Tooltip to name the action on hover or focus — useful since the buttons are icon-only. Render the button through Tooltip.Trigger's render prop so it stays a welded segment of the group; a shared Tooltip.Provider makes the row's tooltips open instantly once one is shown.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { Tooltip } from "@stridge/noctis/tooltip";
import { useState } from "react";
export default function PaginationTooltips() {
const [page, setPage] = useState(3);
return (
<Tooltip.Provider>
<Pagination.Root page={page} onPageChange={setPage} total={250} perPage={25}>
<Pagination.Info />
<Pagination.Controls>
<Tooltip.Root>
<Tooltip.Trigger render={<Pagination.First />} />
<Tooltip.Popup>First page</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger render={<Pagination.Previous />} />
<Tooltip.Popup>Previous page</Tooltip.Popup>
</Tooltip.Root>
<Pagination.Page />
<Tooltip.Root>
<Tooltip.Trigger render={<Pagination.Next />} />
<Tooltip.Popup>Next page</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger render={<Pagination.Last />} />
<Tooltip.Popup>Last page</Tooltip.Popup>
</Tooltip.Root>
</Pagination.Controls>
</Pagination.Root>
</Tooltip.Provider>
);
}
Custom info text
Pagination.Info renders "Showing X–Y of Z" by default. Pass a render function as its children for custom text — e.g. "Page X of Y" — receiving the page, page count, range, and totals.
"use client";
import { Pagination } from "@stridge/noctis/pagination";
import { useState } from "react";
export default function PaginationCustomInfo() {
const [page, setPage] = useState(3);
return (
<Pagination.Root page={page} onPageChange={setPage} pageCount={12}>
<Pagination.Info>{({ page, pageCount }) => `Page ${page} of ${pageCount}`}</Pagination.Info>
<Pagination.Controls>
<Pagination.First />
<Pagination.Previous />
<Pagination.Page />
<Pagination.Next />
<Pagination.Last />
</Pagination.Controls>
</Pagination.Root>
);
}
Keyboard
The controls are standard buttons and an input, so they follow native semantics.
| Key | Action |
|---|---|
| Tab | Move between the page field, the nav buttons, and the page-size select. |
| Enter | Commit a typed page in Pagination.Page (clamped to the valid range). |
| Space / Enter | Activate the focused nav button. |
Disabled boundary buttons (previous/first on page 1, next/last on the last page) are skipped by Tab. Nav chevrons mirror under RTL.
Accessibility
Pagination.Controls renders a <nav> landmark labelled "Pagination" (override via the root's labels). Each nav button is icon-only with an accessible name (First page, Previous page, …); Pagination.Page and Pagination.PageSize carry their own labels. Pagination.Info is an aria-live="polite" region, so the range is announced as the page changes. All names flow through @stridge/noctis-intl — override per instance with the labels prop, or globally with a locale dictionary.
Anatomy
Compose the bar from its parts; Pagination.Root is controlled and shares the page model through context.
Pagination.Root— ownspage/onPageChange,total+perPage(orpageCount),size, andlabels.Pagination.Info— the "Showing X–Y of Z" status line; pass a render function for custom text.Pagination.PageSize— a layout cluster for the page-size picker; composePagination.PageSizeLabel(the "Per page" text) andPagination.PageSizeSelect(theSelect,value/onValueChange) inside.Pagination.Separator— a vertical hairline between clusters.Pagination.Controls— the<nav>landmark wrapping a weldedButtonGroup.Pagination.First/Previous/Next/Last— bounds-aware nav buttons.Pagination.Page— the editable page-number field.
Pagination owns the noctis-pagination, noctis-pagination-info, noctis-pagination-page-size, noctis-pagination-page-size-label, noctis-pagination-page, noctis-pagination-controls, and noctis-pagination-separator slots; the hosted controls keep their own noctis-button / noctis-input / noctis-select-* slots.
On surfaces
The same control re-tuned across the elevation scopes — the root canvas, an elevated panel, a menu, and a sunken well. It stays legible on every layer.
Design tokens
Generated from the component's declaration — the same graph that mints the CSS. Pagination mints only its own layout and chrome (the row gap, the info type, the page-field width, the separator); the hosted controls carry their own tokens. Set one on any ancestor to retune every pagination in that region. See Customization for the override ladder and Tokens for the whole graph.
API reference
Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table. Expand a row for the full type and description.