A reusable, flexible pagination component for the SolidStart frontend that supports both offset-based and cursor-based pagination.
Overview
The <Pagination> component exposes two distinct modes via the type prop:
type="offset": Traditional numbered pagination with ellipsis support (1, 2, ..., 9, 10). Ideal for admin panels, reports, and when users need to jump to arbitrary pages.
type="cursor": Compact navigation for infinite scroll or real-time datasets. Shows simple Previous/Next buttons. Ideal for chat logs, feeds, and highly dynamic lists where offset skipping is inefficient or inaccurate.
Usage
import { Pagination } from "@monorepo/solid-pkg-ui";
import { createSignal } from "solid-js";
function UsersList() {
const [currentPage, setCurrentPage] = createSignal(1);
const [pageSize, setPageSize] = createSignal(10);
// Example state (in reality, from your query)
const totalPages = 10;
const totalRecords = 95;
const hasNext = currentPage() < totalPages;
const hasPrev = currentPage() > 1;
return (
<Pagination
type="offset"
currentPage={currentPage()}
totalPages={totalPages}
onPageChange={setCurrentPage}
hasNext={hasNext}
hasPrev={hasPrev}
pageSize={pageSize()}
onPageSizeChange={setPageSize}
totalRecords={totalRecords}
showGoTo={true}
/>
);
}
import { Pagination } from "@monorepo/solid-pkg-ui";
import { createSignal } from "solid-js";
function ChatHistory() {
const [pageSize, setPageSize] = createSignal(20);
// Example state from your API response
const hasNext = true;
const hasPrev = false;
const handleNext = () => {
/* Fetch using nextCursor */
};
const handlePrev = () => {
/* Fetch using prevCursor */
};
return (
<Pagination
type="cursor"
hasNext={hasNext}
hasPrev={hasPrev}
onNext={handleNext}
onPrevious={handlePrev}
pageSize={pageSize()}
onPageSizeChange={(newSize) => {
// Must reset cursor state here
setPageSize(newSize);
}}
/>
);
}
Props Reference
| Prop | Type | Default | Mode | Description |
|---|
type | 'offset' | 'cursor' | (Required) | Both | The pagination mode. |
pageSize | number | (Required) | Both | The current number of items per page. |
onPageSizeChange | (size: number) => void | - | Both | Callback when the user selects a new page size. If omitted, the page size selector is hidden. |
pageSizeOptions | number[] | [10, 20, 50, 100] | Both | Available options in the page size dropdown. |
totalRecords | number | - | Both | The total number of records across all pages. Only shown when explicitly passed; never inferred. |
theme | 'auto' | 'dark' | 'auto' | Both | UI theme. auto respects OS/system preference. dark forces dark mode. |
currentPage | number | (Required) | Offset | The active page (1-indexed). Optional in cursor mode (for display only). |
totalPages | number | (Required) | Offset | Total number of pages available. |
onPageChange | (page: number) => void | (Required) | Offset | Callback when a page number or go-to input is submitted. |
hasNext | boolean | (Required) | Both | Disables the “Next” button when false. |
hasPrev | boolean | (Required) | Both | Disables the “Previous” button when false. |
siblingCount | number | 1 | Offset | The number of page buttons to show on each side of the active page. |
showGoTo | boolean | true | Offset | Whether to show the “Go to page” input box. |
onNext | () => void | (Required) | Cursor | Callback when “Next” is clicked. |
onPrevious | () => void | - | Cursor | Callback when “Previous” is clicked. |
Ellipsis and siblingCount
The type="offset" component automatically inserts ellipsis (...) when there are large gaps between pages. The siblingCount prop controls how many page numbers appear directly adjacent to the active page.
For example, with totalPages={10} and siblingCount={1}:
- On page 1:
1, 2, ..., 10
- On page 5:
1, ..., 4, 5, 6, ..., 10
- On page 9:
1, ..., 8, 9, 10
If the gap between numbers is exactly 1 (e.g. 1, 2, 3, 4, ..., 7), no ellipsis is rendered between them.
Important Considerations
Page Size Changes in Cursor Mode
When using type="cursor" alongside onPageSizeChange, you must reset your cursor state whenever the page size is altered.Because cursor tokens point to specific boundaries within the dataset, changing the limit (page size) invalidates the existing cursor tokens.
Theme Support
theme="auto": Relies on Tailwind’s dark: classes. The component switches to dark mode if the parent <html> element has the dark class, or via prefers-color-scheme.
theme="dark": The component forces the dark theme locally. Useful for embedding the pagination in dark-only sections of the UI, overriding the global theme.
Types
The UI component aligns with the canonical PaginationMeta interface defined in the core package:
// packages/core/src/types/pagination.ts
export interface PaginationMeta {
mode: "offset" | "cursor";
limit: number;
hasNext: boolean;
hasPrev: boolean;
// Offset fields
page?: number;
totalPages?: number;
totalRecords?: number;
// Cursor fields
nextCursor?: string;
prevCursor?: string;
}
export interface PaginatedResponse<T> {
items: T[];
pagination: PaginationMeta;
}