Table
Semantic table primitives with a pagination bar and hooks for sorting, filtering, and paging data.
Table and its subcomponents render native table, thead, tbody, tfoot, tr, td, and th elements through Reactberry's Text element. Use them for tabular data, with TablePagination and the hooks for in-memory sorting, filtering and paging. TablePagination and the three hooks are client-side ("use client"); the hooks hold state with React useState.
Invoice | Customer | Status | Amount |
|---|---|---|---|
| INV-1001 | Northwind | Paid | $1,250.00 |
| INV-1002 | Acme Corp | Pending | $480.00 |
| INV-1003 | Globex | Paid | $2,100.00 |
Import#
import {
Table,
TableHeader,
TableBody,
TableFooter,
TableRow,
TableCell,
TableHeaderCell,
TablePagination,
useTableControls,
useTableFilters,
useFilteredTableControls,
} from "@reactberry/system/blocks";
Usage#
import {
Table,
TableHeader,
TableBody,
TableRow,
TableCell,
TableHeaderCell,
TablePagination,
useTableControls,
} from "@reactberry/system/blocks";
import { Box, Button } from "@reactberry/system/elements";
type User = {
id: number;
name: string;
email: string;
age: number;
status: "active" | "inactive";
};
export default function UsersTable({ users }: { users: User[] }) {
const controls = useTableControls({ data: users, initialItemsPerPage: 10 });
const toggleSort = (key: string) =>
controls.handleSort(
key,
controls.sortKey === key && controls.sortDirection === "asc" ? "desc" : "asc",
);
return (
<Box>
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>
<Button as="button" type="button" variant="ghost" $size="small" onClick={() => toggleSort("name")}>
Name
</Button>
</TableHeaderCell>
<TableHeaderCell>Email</TableHeaderCell>
<TableHeaderCell align="right">Age</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{controls.currentData.map((user) => (
<TableRow key={user.id}>
<TableCell align="left">{user.name}</TableCell>
<TableCell align="left">{user.email}</TableCell>
<TableCell align="right">{user.age}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<TablePagination
currentPage={controls.currentPage}
totalPages={controls.totalPages}
totalItems={controls.totalItems}
itemsPerPage={controls.itemsPerPage}
onPageChange={controls.handlePageChange}
showPageSize
onPageSizeChange={controls.handlePageSizeChange}
/>
</Box>
);
}
Examples#
Sorting and page size#
useTableControls sorts and paginates rows in memory. Build sort controls inside TableHeaderCell; showPageSize with onPageSizeChange adds a page size select.
Role | ||
|---|---|---|
| Aisha Khan | Product manager | 3 |
| Leo Martins | Engineer | 7 |
| Maya Chen | Designer | 4 |
| Noah Kim | Engineer | 6 |
| Sara Lind | Researcher | 2 |
| Tom Becker | Engineer | 5 |
Filtering#
useFilteredTableControls filters rows with one predicate per filter key, then sorts and paginates the result. data holds the rows for the current page.
Order | Customer | Status |
|---|---|---|
| #4821 | Emma Wilson | open |
| #4822 | Lucas Brown | shipped |
| #4823 | Olivia Davis | shipped |
| #4824 | Liam Garcia | open |
Footer row#
TableFooter renders a tfoot; colSpan is passed to the td.
Item | Qty | Price |
|---|---|---|
| Pro plan (annual) | 1 | $240 |
| Extra seats | 5 | $300 |
| Priority support | 1 | $99 |
| Total | $639 | |
API#
Table#
Renders Text with as="table" and width="100%". All props (HTML table attributes and Text/Box style props) are spread onto it after the defaults.
TableHeader, TableBody, TableFooter#
Render Text as thead, tbody, and tfoot respectively. They add no styles; all props are spread onto the element.
TableRow#
Renders Text as tr. All props are spread onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
selected | boolean | — | Declared in the type; not used by the component and forwarded to the DOM. |
clickable | boolean | — | Declared in the type; not used by the component and forwarded to the DOM. |
width | string | number | — | Forwarded to |
TableCell#
Renders a td via Text with position="relative", border="1px solid", and a fixed height="2rem". Forwards its ref to the td.
| Prop | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "center" | Mapped to |
truncate | boolean | true | When children are a string or number, they are wrapped in a |
colSpan | number | — | Passed to the |
rowSpan | number | — | Passed to the |
width | string | number | — | Passed to |
header | boolean | — | Declared in the type; not used. The cell always renders a |
Remaining props are spread onto the td before width, colSpan, rowSpan, and height, so height cannot be overridden.
TableHeaderCell#
Renders a th via Text with skin="row.header", border="1px solid", borderColor="surface", fontSize="xxs", and fontWeight="700". Children are wrapped in an inner div.
| Prop | Type | Default | Description |
|---|---|---|---|
align | "left" | "center" | "right" | "left" | Mapped to |
width | string | number | "auto" | Applied to the |
colSpan | number | — | Passed to the |
rowSpan | number | — | Passed to the |
sortable | boolean | — | Declared as "show a visual sort indicator"; no indicator is rendered and the prop is forwarded to the DOM. |
truncate | boolean | — | Not destructured; it reaches |
Remaining props are spread onto the th after the default styles.
TablePagination#
A client component that renders an item count, an optional page size select, and previous/next and page number buttons. No other props are accepted.
| Prop | Type | Default | Description |
|---|---|---|---|
currentPage* | number | — | 0-based current page. |
totalPages* | number | — | Total page count. |
totalItems* | number | — | Total item count, used in the "Showing X-Y of N items" text. |
itemsPerPage* | number | — | Page size; also the selected value of the page size |
onPageChange* | (page: number) => void | — | Called with the 0-based target page. |
showPageSize | boolean | false | Shows the page size |
pageSizeOptions | number[] | [10, 25, 50, 100] | Options for the page size |
onPageSizeChange | (pageSize: number) => void | — | Called with the selected page size. |
showItemCount | boolean | true | Shows the item count text. |
maxPageButtons | number | 5 | When |
useTableControls#
Sorts and paginates an array in memory. The default sort compares row[key]; strings are compared case-insensitively with localeCompare, and null/undefined values sort last.
Parameters
useTableControls(options) takes a UseTableControlsOptions<T> object:
| Prop | Type | Default | Description |
|---|---|---|---|
data* | T[] | — | Source rows. |
initialItemsPerPage | number | 10 | Initial page size. |
initialSortKey | string | null | null | Initial sort key. |
initialSortDirection | "asc" | "desc" | null | null | Initial sort direction. |
customSort | (data: T[], key: string, direction: "asc" | "desc") => T[] | — | Replaces the default sort. |
Returns
UseTableControlsReturn<T>:
| Prop | Type | Default | Description |
|---|---|---|---|
currentData | T[] | — | Rows for the current page after sorting. |
sortKey | string | null | — | Current sort key. |
sortDirection | "asc" | "desc" | null | — | Current sort direction. |
handleSort | (key: string, direction: SortDirection) => void | — | Sets the sort; a |
currentPage | number | — | 0-based page, clamped to the last valid page. |
totalPages | number | — | Page count. |
totalItems | number | — | Row count after sorting. |
itemsPerPage | number | — | Current page size. |
handlePageChange | (page: number) => void | — | Sets the page. |
handlePageSizeChange | (pageSize: number) => void | — | Sets the page size and resets to page 0. |
resetPagination | () => void | — | Restores page 0 and |
resetSorting | () => void | — | Restores |
resetAll | () => void | — | Runs both resets. |
useTableFilters#
Filters an array with one predicate per filter key.
Parameters
useTableFilters(options) takes a UseTableFiltersOptions<T, F> object:
| Prop | Type | Default | Description |
|---|---|---|---|
data* | T[] | — | Rows to filter. |
filterFunctions* | { [K in keyof F]: (item: T, value: F[K]) => boolean } | — | One predicate per filter key. |
initialFilters* | F | — | Initial filter values. |
Returns
UseTableFiltersReturn<T, F>:
| Prop | Type | Default | Description |
|---|---|---|---|
filteredData | T[] | — | Rows that pass every predicate. |
filters | F | — | Current filter values. |
updateFilter | (key: K, value: F[K]) => void | — | Merges one new value. |
updateFilters | (filters: Partial<F>) => void | — | Merges several new values. |
resetFilters | () => void | — | Restores |
hasActiveFilters | boolean | — |
|
useFilteredTableControls#
useFilteredTableControls(filterOptions, controlOptions) runs useTableFilters with filterOptions and passes filteredData into useTableControls. controlOptions is Omit<UseTableControlsOptions<T>, "data">. It returns every field from both hooks plus data, which equals currentData.
Accessibility#
- All parts render native table elements (
table,thead,tbody,tfoot,tr,th,td). - Pagination buttons are
Buttonelements withoutas="button", so they render asspanwithrole="button"andtabIndex={0}. They have no keyboard handler, anddisabledonly applies styles (pointer-events: none). - Page buttons have
aria-labelvalues such as"Go to page 2"and the active page setsaria-current="page". The previous and next buttons are labelled"Previous page"and"Next page". - The page size
selectis a native element with inline styles and is not programmatically labelled by the "Items per page:" text.
Notes#
- There is no
SortableTableHeaderexport andTablehas nostriped,hoverable,compact,bordered, orstickyHeaderprops, despite references insrc/blocks/Table/README.mdandsrc/docs/api/blocks/Table.md. filterFunctionsandinitialFiltersare memo dependencies; define them outside the component or memoise them to avoid recomputation on every render.- Changing filters does not reset the page, but
useTableControlsclampscurrentPageto the last valid page. - In
TablePagination, the next button is disabled only whencurrentPage === totalPages - 1; withtotalPagesof0it remains enabled.