Skip to main contentSkip to docs navigation

Compact Pagination

A dense table-footer pagination bar — page size, a range summary, and a page-number picker instead of a row of numbered buttons.

On this page

Some data tables need a denser footer than <Pagination>'s numbered-button row: a page size control, a "1-10 of 100" range summary, and Prev/Next flanking a page picker. This isn't a mode of <Pagination> — the controls here (Select, Button) aren't grouped into one joined pill the way <Pagination>'s page-link list is, so it's composed directly from existing primitives instead.

1-10 of 100
JSX
import { useState, type ChangeEvent } from 'react'
import { Button, Icon, Select, usePagination } from '@chassis-ui/react'

export const Example = () => {
  const total = 100
  const [pageSize, setPageSize] = useState(10)
  const [page, setPage] = useState(1)
  const totalPages = Math.ceil(total / pageSize)
  const start = (page - 1) * pageSize + 1
  const end = Math.min(page * pageSize, total)

  const prevDisabled = page <= 1
  const nextDisabled = page >= totalPages
  const { prevRef, nextRef, handlePrevClick, handleNextClick } = usePagination<HTMLButtonElement>({
    prevDisabled,
    nextDisabled,
    onPrev: () => setPage((p) => p - 1),
    onNext: () => setPage((p) => p + 1)
  })

  const handlePageSizeChange = (e: ChangeEvent<HTMLSelectElement>) => {
    setPageSize(Number(e.target.value))
    setPage(1)
  }

  return (
    <div className="d-flex align-items-center justify-content-between gap-md">
      <div className="d-flex flex-fill gap-xs">
        <label htmlFor="compact-pg-size" className="mb-0">
          Items per page:
        </label>
        <Select
          id="compact-pg-size"
          size="sm"
          className="w-auto"
          value={String(pageSize)}
          onChange={handlePageSizeChange}
          options={[
            { label: '10', value: '10' },
            { label: '25', value: '25' },
            { label: '50', value: '50' }
          ]}
        />
      </div>
      <small className="fg-subtle">{`${start}-${end} of ${total}`}</small>
      <div className="d-flex flex-fill gap-xs justify-content-end">
        <Button
          ref={prevRef}
          className="icon-only"
          size="sm"
          disabled={prevDisabled}
          onClick={handlePrevClick}
        >
          <Icon name="chevron-left-solid" className="directional-icon" />
          <span className="visually-hidden">Previous page</span>
        </Button>
        <Select
          aria-label="Current page"
          size="sm"
          className="w-auto"
          value={String(page)}
          onChange={(e) => setPage(Number(e.target.value))}
          options={Array.from({ length: totalPages }, (_, i) => ({
            label: String(i + 1),
            value: String(i + 1)
          }))}
        />
        <Button
          ref={nextRef}
          className="icon-only"
          size="sm"
          disabled={nextDisabled}
          onClick={handleNextClick}
        >
          <Icon name="chevron-right-solid" className="directional-icon" />
          <span className="visually-hidden">Next page</span>
        </Button>
      </div>
    </div>
  )
}

Key points

  • Select handles both the page size and page number pickers — same component, same pattern as the Paginated Table example's page size control.
  • Prev/Next are plain Buttons with the icon-only class, not <Pagination>/PaginationItem — there's no numbered-button list here for that markup to join into.
  • usePagination is what makes the Prev/Next buttons safe to disable at the range's edges. Browsers blur a button the instant it's disabled, so clicking Next into the last page would otherwise silently drop keyboard focus to <body>. The hook redirects focus to the still-enabled sibling instead — pass it the element type your buttons render as (Button renders an HTMLButtonElement), call markPrevClicked/markNextClicked from each onClick before changing state, and attach prevRef/nextRef to the two buttons.
  • Changing pageSize resets page to 1, same reasoning as the Paginated Table pattern: avoid landing on a page that no longer exists.