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

Offset-Based Pagination

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}
    />
  );
}

Cursor-Based Pagination

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

PropTypeDefaultModeDescription
type'offset' | 'cursor'(Required)BothThe pagination mode.
pageSizenumber(Required)BothThe current number of items per page.
onPageSizeChange(size: number) => void-BothCallback when the user selects a new page size. If omitted, the page size selector is hidden.
pageSizeOptionsnumber[][10, 20, 50, 100]BothAvailable options in the page size dropdown.
totalRecordsnumber-BothThe total number of records across all pages. Only shown when explicitly passed; never inferred.
theme'auto' | 'dark''auto'BothUI theme. auto respects OS/system preference. dark forces dark mode.
currentPagenumber(Required)OffsetThe active page (1-indexed). Optional in cursor mode (for display only).
totalPagesnumber(Required)OffsetTotal number of pages available.
onPageChange(page: number) => void(Required)OffsetCallback when a page number or go-to input is submitted.
hasNextboolean(Required)BothDisables the “Next” button when false.
hasPrevboolean(Required)BothDisables the “Previous” button when false.
siblingCountnumber1OffsetThe number of page buttons to show on each side of the active page.
showGoTobooleantrueOffsetWhether to show the “Go to page” input box.
onNext() => void(Required)CursorCallback when “Next” is clicked.
onPrevious() => void-CursorCallback 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;
}