Skip to Content
Hashti Design System
Components

Pagination

Control bar navigating partitioned datasets across indexed pages.

Pagination

Pagination partitions large, dense datasets into manageable index pages. It provides users with clear spatial awareness of dataset size, current item range, and responsive controls to navigate forward, backward, or adjust page size.

In Hashti, Pagination renders as an accessible <nav> landmark with live region item counting, disabled edge boundaries, and an integrated native select dropdown for page size configuration.

PaginationMulti-page collection navigation with ellipsis bounds.
Viewing page 2 of 8

Import

import { Pagination, NativeSelect, paginationRange } from "@hashti/react";

Examples

Basic Pagination

Configure pagination by specifying totalItems, the current page (1-indexed), the pageSize, and the onPageChange callback:

import { useState } from "react";
import { Pagination } from "@hashti/react";

export function BasicPaginationExample() {
  const [currentPage, setCurrentPage] = useState(1);

  return (
    <div className="space-y-4 max-w-2xl">
      <div className="p-6 border border-subtle rounded bg-layer-1 text-sm">
        Displaying records for page <strong>{currentPage}</strong>...
      </div>

      <Pagination totalItems={100} page={currentPage} pageSize={10} onPageChange={setCurrentPage} />
    </div>
  );
}

With Page Size Selector

Provide onPageSizeChange and pageSizes to enable users to toggle how many records are displayed per page:

import { useState } from "react";
import { Pagination } from "@hashti/react";

export function PageSizePaginationExample() {
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);

  return (
    <div className="space-y-4 max-w-2xl">
      <Pagination
        totalItems={250}
        page={page}
        pageSize={pageSize}
        pageSizes={[10, 20, 50, 100]}
        onPageChange={setPage}
        onPageSizeChange={(newSize) => {
          setPageSize(newSize);
          setPage(1); // Reset to page 1 when size changes
        }}
      />
    </div>
  );
}

Table & Pagination Integration

Combine <Table> and <Pagination> for a full data browsing experience:

import { useState } from "react";
import { Table, TableHeader, TableBody, Column, Row, Cell, Pagination } from "@hashti/react";

const allRecords = Array.from({ length: 47 }, (_, i) => ({
  id: `svc-${i + 1}`,
  name: `service-node-${String(i + 1).padStart(2, "0")}`,
  region: i % 2 === 0 ? "me-central-1" : "eu-west-1",
  requests: `${(Math.random() * 50 + 10).toFixed(1)}k req/s`,
}));

export function TablePaginationExample() {
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(5);

  const start = (page - 1) * pageSize;
  const currentRecords = allRecords.slice(start, start + pageSize);

  return (
    <div className="space-y-3">
      <Table aria-label="Microservices Fleet">
        <TableHeader>
          <Column isRowHeader>Node Name</Column>
          <Column>Cluster Region</Column>
          <Column>Incoming Request Rate</Column>
        </TableHeader>
        <TableBody items={currentRecords}>
          {(item) => (
            <Row id={item.id}>
              <Cell>{item.name}</Cell>
              <Cell>{item.region}</Cell>
              <Cell>{item.requests}</Cell>
            </Row>
          )}
        </TableBody>
      </Table>

      <Pagination
        totalItems={allRecords.length}
        page={page}
        pageSize={pageSize}
        pageSizes={[5, 10, 20]}
        onPageChange={setPage}
        onPageSizeChange={(size) => {
          setPageSize(size);
          setPage(1);
        }}
      />
    </div>
  );
}

Standalone NativeSelect

The <NativeSelect> component used internally by Pagination is also exported for lightweight, native HTML select controls:

import { useState } from "react";
import { NativeSelect } from "@hashti/react";

export function NativeSelectExample() {
  const [region, setRegion] = useState("tehran");

  return (
    <div className="max-w-xs">
      <NativeSelect
        label="Data Center Region"
        value={region}
        onChange={(e) => setRegion(e.target.value)}
        description="Select the closest edge cluster to your users."
      >
        <option value="tehran">Tehran (IR-THR-1)</option>
        <option value="isfahan">Isfahan (IR-ISF-1)</option>
        <option value="shiraz">Shiraz (IR-SHR-1)</option>
        <option value="frankfurt">Frankfurt (EU-FRA-1)</option>
      </NativeSelect>
    </div>
  );
}

Props Reference

Pagination Props

PropTypeDefaultDescription
totalItemsnumberRequiredThe total number of items across the entire dataset.
pagenumberRequiredThe current active page number (1-indexed).
pageSizenumberRequiredThe number of records displayed per page.
onPageChange(page: number) => voidRequiredCallback fired when the previous or next navigation button is pressed.
pageSizesnumber[][10, 20, 50]Options populated in the items-per-page select dropdown.
onPageSizeChange(size: number) => void—Callback fired when the user selects a new page size. When omitted, the page size dropdown is hidden.
aria-labelstring'Pagination'Accessible landmark label for the pagination <nav>.
classNamestring—Additional CSS class names.

NativeSelect Props

PropTypeDefaultDescription
labelReactNodeRequiredVisible label associated with the native <select> element.
size'sm' | 'md' | 'lg''md'Height and padding density of the select input.
descriptionReactNode—Supporting helper text rendered beneath the select.
errorMessageReactNode—Validation error text. When present, marks the select invalid.
disabledbooleanfalseWhether the select input is disabled.

Accessibility (a11y)

  • Navigation Landmark: The pagination bar renders as a semantic <nav aria-label="Pagination">, making it discoverable through screen reader landmark navigation menus.
  • Polite Live Range Counter: The item summary span (1–10 of 100 items) is marked with aria-live="polite". When users navigate between pages, the newly updated item range is read aloud without disrupting other announcements.
  • Button States:
    • The “Previous page” button is automatically assigned isDisabled={page <= 1}.
    • The “Next page” button is assigned isDisabled={page >= pages}.
    • Disabled buttons are announced as disabled and removed from the active keyboard tab cycle.
  • Keyboard Navigation: Buttons and the page size selector are fully focusable and activatable via standard Tab, Enter, and Space keys.

Keyboard Interactions

KeyAction
TabMoves focus through pagination buttons and select field.
Enter / SpaceActivates next, previous, or numbered page.

WAI-ARIA & Data Attributes

AttributeTargetDescription
[data-hovered]Page ButtonPresent on pointer hover.
[data-selected]Page ButtonActive on current active page button.
[data-disabled]Page ButtonApplied when button reaches first or last page boundary.