Skip to Content
BlocksSearchableTableBlock

SearchableTableBlock

A records table with configurable columns, search, sortable headers, selection and pagination.

Import

import { SearchableTableBlock } from "@raydenui/ui/blocks"; import type { SearchableTableColumn, SearchableTableRow } from "@raydenui/ui/blocks";

Usage

Each row is a Record<string, unknown> that must carry a string id. Columns look up values by key.

const columns: SearchableTableColumn[] = [ { key: "name", label: "Name", sortable: true }, { key: "category", label: "Category" }, { key: "price", label: "Price", sortable: true, width: "140px" }, ]; const rows: SearchableTableRow[] = [ { id: "sku-1", name: "Everyday ceramic cup", category: "Objects", price: "£24.00" }, ]; <SearchableTableBlock columns={columns} rows={rows} searchPlaceholder="Search products…" />;

Local or consumer-owned search and sort

Search and sort are local by default. Supplying a handler moves that concern to you:

  • Supply onSearch and the block stops filtering rows itself; it renders exactly what you pass.
  • Supply onSort and the block only renders the sort indicator; you reorder rows.

The two halves cannot be mixed for one concern.

<SearchableTableBlock columns={columns} rows={remoteRows} onSearch={(query) => search(query)} onSort={(key, direction) => reorder(key, direction)} />

Two toolbars

Passing title renders a title-and-icon toolbar. Omitting it renders a search-input toolbar. In the title variant, the search and sort buttons render only when onSearchClick and onSortClick are supplied.

<SearchableTableBlock title="Products" columns={columns} rows={rows} onSearchClick={() => openSearchPanel()} onSortClick={() => openSortPanel()} />

Filter and date selector need handlers

The filter button appears whenever onFilter is supplied; the date selector appears whenever onDateSelect is supplied. showFilter and showDateSelector can hide them, but setting either true without its handler does not bring it back — the block does not render a control that has nowhere to go.

<SearchableTableBlock columns={columns} rows={rows} onFilter={() => openFilterPanel()} onDateSelect={() => openDatePicker()} />
<SearchableTableBlock columns={columns} rows={rows} onFilter={() => openFilterPanel()} showFilter={false} />

Custom cell rendering

render receives the cell value first, then the whole row.

const columns: SearchableTableColumn[] = [ { key: "name", label: "Name", sortable: true }, { key: "actions", label: "Actions", render: (value, row) => ( <Button size="sm" variant="secondary" onClick={() => edit(String(row.id))}> Edit </Button> ), }, ];

Selection

const [selectedIds, setSelectedIds] = useState<string[]>([]); <SearchableTableBlock columns={columns} rows={rows} selectable selectedIds={selectedIds} onSelectionChange={setSelectedIds} />;

Pagination

const [page, setPage] = useState(1); <SearchableTableBlock columns={columns} rows={pageRows} page={page} totalPages={totalPages} onPageChange={setPage} />;

Selection reconciliation contract

An uncontrolled selection covers only the rows the block is currently given. When rows changes, any selected id that is no longer present is dropped permanently.

  • Deleting a row deselects it; replacing rows with new ids clears the selection, so the header never reports a stale “all selected” over unchecked rows.
  • A filter the block applies itself keeps the selection of rows that left view, because the whole data set is still rows.
  • A filter or page you apply outside the block hands over a different rows array, so the selections that left with it are dropped.
  • Reconciliation is never silent: dropping ids fires onSelectionChange with what remains.

A controlled selection is never reconciled — supply selectedIds to keep a selection across pages, or across a filter you apply yourself.

Full and partial selection are derived from membership of the rows on screen, never from set sizes.

Props

PropTypeDefaultDescription
columnsSearchableTableColumn[]—Required. Column definitions.
rowsSearchableTableRow[]—Required. Each row needs a string id.
titlestring—Renders the title toolbar instead of the search-input toolbar.
searchPlaceholderstring—Search input placeholder.
onSearch(query: string) => void—Supplying it puts search under your control.
onSort(key: string, direction: SortDirection) => void—Supplying it puts ordering under your control.
onSearchClick() => void—Title toolbar only. The button renders only when supplied.
onSortClick() => void—Title toolbar only. The button renders only when supplied.
showFilterboolean—Hides the filter button. Cannot show it without onFilter.
onFilter() => void—Required for the filter button to appear.
showDateSelectorboolean—Hides the date selector. Cannot show it without onDateSelect.
onDateSelect() => void—Required for the date selector to appear.
selectablebooleanfalseEnable row selection checkboxes.
selectedIdsstring[]—Controlled selection. Never reconciled.
onSelectionChange(ids: string[]) => void—Fired with the full resulting selection, including on reconciliation.
onRowAction(rowId: string) => void—Row kebab menu handler. The column renders only when supplied.
pagenumber—Current page, 1-indexed.
totalPagesnumber—Total pages.
onPageChange(page: number) => void—Page change handler.
classNamestring—Additional class names.

SearchableTableColumn

MemberTypeRequiredDescription
keystringYesUnique column key, used to look up row values.
labelstringYesColumn header label.
sortablebooleanNoEnable sorting on this column.
widthstringNoFixed column width as a CSS string, e.g. "300px".
render(value: unknown, row: Record<string, unknown>) => ReactNodeNoCustom cell renderer. Value first, then the row.

SearchableTableRow

Record<string, unknown> & { id: string }.

States

  • default — the record table.
  • no matches — local search matched nothing.
  • partial selection — the select-all control reports aria-checked="mixed".
  • no configured actions — filter, date selector, row actions and pagination are absent rather than inert.

There is no loading or error state. Render your own around the block, or pass an empty rows array.

Accessibility

  • Native table structure with column scopes and aria-sort on sortable headers.
  • A keyboard-focusable, bounded horizontal scroll region for the table itself.
  • Named selection checkboxes and named row action controls.
  • A visually hidden “Actions” heading for the action column.

Known limitations

  • Search and sort cannot be partly local and partly consumer-owned for the same concern.
  • There is no built-in filter UI. onFilter opens a panel you provide.
Last updated on