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
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
Selecthandles 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 theicon-onlyclass, not<Pagination>/PaginationItem— there's no numbered-button list here for that markup to join into. usePaginationis 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 (Buttonrenders anHTMLButtonElement), callmarkPrevClicked/markNextClickedfrom eachonClickbefore changing state, and attachprevRef/nextRefto the two buttons.- Changing
pageSizeresetspageto1, same reasoning as the Paginated Table pattern: avoid landing on a page that no longer exists.