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-1001NorthwindPaid$1,250.00
INV-1002Acme CorpPending$480.00
INV-1003GlobexPaid$2,100.00
Showing 1-3 of 7 items
←123→

Import#

typescript
import {
  Table,
  TableHeader,
  TableBody,
  TableFooter,
  TableRow,
  TableCell,
  TableHeaderCell,
  TablePagination,
  useTableControls,
  useTableFilters,
  useFilteredTableControls,
} from "@reactberry/system/blocks";

Usage#

typescript
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 KhanProduct manager3
Leo MartinsEngineer7
Maya ChenDesigner4
Noah KimEngineer6
Sara LindResearcher2
Tom BeckerEngineer5
Showing 1-6 of 6 items
Items per page:
←1→

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
#4821Emma Wilsonopen
#4822Lucas Brownshipped
#4823Olivia Davisshipped
#4824Liam Garciaopen
Showing 1-4 of 6 items
←12→

TableFooter renders a tfoot; colSpan is passed to the td.

Item
Qty
Price
Pro plan (annual)1$240
Extra seats5$300
Priority support1$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.

PropTypeDefaultDescription
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 Text as a style prop.

TableCell#

Renders a td via Text with position="relative", border="1px solid", and a fixed height="2rem". Forwards its ref to the td.

PropTypeDefaultDescription
align
"left" | "center" | "right"
"center"

Mapped to textAlign.

truncate
boolean
true

When children are a string or number, they are wrapped in a Text with fontSize="s" and single-line truncation; the full value is set as title.

colSpan
number
—

Passed to the td.

rowSpan
number
—

Passed to the td.

width
string | number
—

Passed to Text as a style prop.

header
boolean
—

Declared in the type; not used. The cell always renders a td and the prop is forwarded to the DOM.

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.

PropTypeDefaultDescription
align
"left" | "center" | "right"
"left"

Mapped to textAlign.

width
string | number
"auto"

Applied to the th and the inner div.

colSpan
number
—

Passed to the th.

rowSpan
number
—

Passed to the th.

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 Text, which applies its truncate styles (including display: inline-block) to the th itself.

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.

PropTypeDefaultDescription
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 select.

onPageChange*
(page: number) => void
—

Called with the 0-based target page.

showPageSize
boolean
false

Shows the page size select. It renders only when onPageSizeChange is also provided.

pageSizeOptions
number[]
[10, 25, 50, 100]

Options for the page size select.

onPageSizeChange
(pageSize: number) => void
—

Called with the selected page size.

showItemCount
boolean
true

Shows the item count text.

maxPageButtons
number
5

When totalPages exceeds this, the first and last pages are always shown with ... around a range centred on the current page.

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:

PropTypeDefaultDescription
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>:

PropTypeDefaultDescription
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 null direction clears the key. Resets to page 0.

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 initialItemsPerPage.

resetSorting
() => void
—

Restores initialSortKey and initialSortDirection.

resetAll
() => void
—

Runs both resets.

useTableFilters#

Filters an array with one predicate per filter key.

Parameters

useTableFilters(options) takes a UseTableFiltersOptions<T, F> object:

PropTypeDefaultDescription
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>:

PropTypeDefaultDescription
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 initialFilters.

hasActiveFilters
boolean
—

true when any value differs (strict equality) from initialFilters.

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 Button elements without as="button", so they render as span with role="button" and tabIndex={0}. They have no keyboard handler, and disabled only applies styles (pointer-events: none).
  • Page buttons have aria-label values such as "Go to page 2" and the active page sets aria-current="page". The previous and next buttons are labelled "Previous page" and "Next page".
  • The page size select is a native element with inline styles and is not programmatically labelled by the "Items per page:" text.

Notes#

  • There is no SortableTableHeader export and Table has no striped, hoverable, compact, bordered, or stickyHeader props, despite references in src/blocks/Table/README.md and src/docs/api/blocks/Table.md.
  • filterFunctions and initialFilters are memo dependencies; define them outside the component or memoise them to avoid recomputation on every render.
  • Changing filters does not reset the page, but useTableControls clamps currentPage to the last valid page.
  • In TablePagination, the next button is disabled only when currentPage === totalPages - 1; with totalPages of 0 it remains enabled.