Skip to main contentSkip to docs navigation

Paginated Table

A complete example of Table and Pagination working together to display and navigate a large data set.

On this page

A common UI pattern: fetch a data array, slice it for the current page, render the table, and show a pagination control beneath it.

12 users total
#NameRoleStatus
1Alice MartinAdminActive
2Bob ChenEditorActive
3Carol WhiteViewerInactive
4David KimEditorActive
5Eve TorresViewerActive
JSX
import { useState, type ChangeEvent } from 'react'
import {
  Table,
  Pagination,
  Select,
  Badge,
  TableHeader,
  TableColumn,
  TableBody,
  TableRow,
  TableCell
} from '@chassis-ui/react'

export const Example = () => {
  const allUsers = [
    { id: 1, name: 'Alice Martin', role: 'Admin', status: 'Active' },
    { id: 2, name: 'Bob Chen', role: 'Editor', status: 'Active' },
    { id: 3, name: 'Carol White', role: 'Viewer', status: 'Inactive' },
    { id: 4, name: 'David Kim', role: 'Editor', status: 'Active' },
    { id: 5, name: 'Eve Torres', role: 'Viewer', status: 'Active' },
    { id: 6, name: 'Frank Lee', role: 'Admin', status: 'Inactive' },
    { id: 7, name: 'Grace Hall', role: 'Editor', status: 'Active' },
    { id: 8, name: 'Hank Patel', role: 'Viewer', status: 'Active' },
    { id: 9, name: 'Iris Clark', role: 'Editor', status: 'Inactive' },
    { id: 10, name: 'Jack Lewis', role: 'Viewer', status: 'Active' },
    { id: 11, name: 'Karen Scott', role: 'Admin', status: 'Active' },
    { id: 12, name: 'Leo Adams', role: 'Editor', status: 'Active' }
  ]
  const statusColor = { Active: 'success', Inactive: 'secondary' } as const
  const [page, setPage] = useState(1)
  const [pageSize, setPageSize] = useState(5)
  const totalPages = Math.ceil(allUsers.length / pageSize)
  const offset = (page - 1) * pageSize
  const rows = allUsers.slice(offset, offset + pageSize)
  const columns = [
    { key: 'id', label: '#' },
    { key: 'name', label: 'Name' },
    { key: 'role', label: 'Role' },
    {
      key: 'status',
      label: 'Status',
      render: (v: string) => <Badge color={statusColor[v as keyof typeof statusColor]}>{v}</Badge>
    }
  ]
  const handleSizeChange = (e: ChangeEvent<HTMLSelectElement>) => {
    setPageSize(Number(e.target.value))
    setPage(1)
  }
  return (
    <div>
      <div className="d-flex justify-content-between align-items-center mb-md">
        <small>{allUsers.length} users total</small>
        <div className="d-flex align-items-center gap-sm">
          <label htmlFor="pg-size" className="form-label mb-0">
            Rows per page
          </label>
          <Select
            id="pg-size"
            style={{ width: 'auto' }}
            value={String(pageSize)}
            onChange={handleSizeChange}
            options={[
              { label: '3', value: '3' },
              { label: '5', value: '5' },
              { label: '10', value: '10' }
            ]}
          />
        </div>
      </div>
      <Table aria-label="Users" hover>
        <TableHeader columns={columns}>
          {(column) => <TableColumn key={column.key}>{column.label}</TableColumn>}
        </TableHeader>
        <TableBody items={rows}>
          {(row) => (
            <TableRow key={row.id}>
              {(columnKey) => {
                const column = columns.find((c) => c.key === columnKey)
                const value = row[columnKey as keyof typeof row]
                return (
                  <TableCell>{column?.render ? column.render(String(value)) : value}</TableCell>
                )
              }}
            </TableRow>
          )}
        </TableBody>
      </Table>
      <div className="d-flex justify-content-end mt-md">
        <Pagination
          pages={totalPages}
          activePage={page}
          onActivePageChange={setPage}
          aria-label="User table pagination"
        />
      </div>
    </div>
  )
}

Key points

  • allUsers.slice(offset, offset + pageSize) computes the visible rows. Replace with an API call that accepts page and pageSize query params.
  • The render function in columns lets you replace raw values with components like <Badge>.
  • Changing pageSize resets the page to 1 to avoid a non-existent page.
  • <Pagination> automatically shows ellipsis when pages exceed maxVisiblePages (default 5).