COMPONENTS/NAVIGATION

Pagination.

Move through a larger collection.

In practice

Page 1 of 12

Controlled page, pageCount and onPageChange. Caps visible page links and disables unavailable navigation.

Installation

Install Coal once. Components and TypeScript declarations are included. React 19 and React DOM 19 are peer dependencies.

Terminal · local package
npm install http://localhost:3100/downloads/chlohal-coal-ui-0.5.0.tgz

This installs the local package. It is not yet published on npm. See installation for stylesheet setup and integration.

Usage

example.tsx
"use client";
import * as React from "react";
import "@chlohal/coal-ui/styles.css";
import { Pagination } from "@chlohal/coal-ui";
export default function Example() {
    const [page, setPage] = React.useState(1);
    return (<div>
          <Pagination page={page} pageCount={12} onPageChange={setPage}/>
          <p aria-live="polite">
            Page {page} of 12
          </p>
        </div>);
}

Source

The original Coal implementation. Use the package export to integrate it.

packages/react/src/pagination.tsx
"use client";
import { Button } from "./button.js";
export type PaginationProps = {
  page: number;
  pageCount: number;
  onPageChange: (page: number) => void;
  label?: string;
};
export function Pagination({
  page,
  pageCount,
  onPageChange,
  label = "Pagination",
}: PaginationProps) {
  const count = Math.max(1, Math.floor(pageCount) || 1);
  const current = Math.max(1, Math.min(count, Math.floor(page) || 1));
  const pages = Array.from(
    new Set([1, current - 1, current, current + 1, count]),
  )
    .filter((n) => n >= 1 && n <= count)
    .sort((a, b) => a - b);
  return (
    <nav aria-label={label} className="coal-pagination">
      <Button
        size="sm"
        variant="ghost"
        disabled={current === 1}
        aria-label="Previous page"
        onClick={() => onPageChange(current - 1)}
      >
        ←
      </Button>
      {pages.map((n, i) => (
        <span className="coal-pagination-item" key={n}>
          {i > 0 && n - pages[i - 1] > 1 && (
            <span aria-hidden="true" className="coal-pagination-ellipsis">
              …
            </span>
          )}
          <Button
            size="sm"
            variant={current === n ? "secondary" : "ghost"}
            aria-current={current === n ? "page" : undefined}
            aria-label={`Page ${n}`}
            onClick={() => onPageChange(n)}
          >
            {n}
          </Button>
        </span>
      ))}
      <Button
        size="sm"
        variant="ghost"
        disabled={current === count}
        aria-label="Next page"
        onClick={() => onPageChange(current + 1)}
      >
        →
      </Button>
    </nav>
  );
}

Accessibility checklist

  • Provide an accessible name for controls, including icon-only buttons.
  • Check keyboard navigation, visible focus and disabled states in your application.
  • Do not rely on color alone to communicate status.