Pagination

@sushindustries/ui

Pages as links with first and last always reachable, and nothing that breaks middle-click.

Last updated Aug 17, 20265 min read
View as MarkdownEdit on GitHub

Examples are the tab where the component is shown doing a job, not demonstrating a prop. The API tab already lists the props.

Viewport width
Phone22remthe floor, the narrowest this promises to work at
tanstacktanstack add https://adamjurek.com/r/tanstack/pagination.jsonshadcnpnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/pagination.json

Press Compare. The frames are real viewports, so a layout that breaks at 320 breaks here too rather than in somebody's hands.

In a page

import { Pagination } from "@sushindustries/ui";

interface Result {
	id: string;
	title: string;
}

export function SearchResults({
	page,
	pageCount,
	results,
}: {
	page: number;
	pageCount: number;
	results: readonly Result[];
}) {
	return (
		<section className="container section">
			<ul className="flex flex-col gap-3">
				{results.map((result) => (
					<li key={result.id}>{result.title}</li>
				))}
			</ul>
			<div className="mt-8">
				<Pagination
					page={page}
					pageCount={pageCount}
					hrefFor={(page) => `?page=${page}`}
				/>
			</div>
		</section>
	);
}
tsx

What this example is not

A drop-in for a client-side router. Without renderLink, every page link is a plain anchor - clicking one is a full navigation, which is correct for a server-rendered results page but not what you want if this sits inside a route that manages page as router state.

UsesIconIconThe glyph set, generated from a Markdown table where every drawing carries its reason. One component, typed names, no icon font.@sushindustries/ui · content
Used byArchiveArchiveA filterable grid with categories, subcategories and tags. Renders its own links through a callback so the router stays yours.@sushindustries/ui · layout
Mentioned inArchiveMarkdown