bimatter-react-table
v0.1.16
Published
React table components for data-heavy BIM and engineering interfaces.
Readme
bimatter-react-table
React table components for data-heavy BIM and engineering interfaces.
bimatter-react-table provides BimatterTable: a typed React table component
with column resizing, sorting, row selection, column and row reordering,
tree-like grouping, calculated totals, sticky header/footer behavior, and
custom rendering hooks.
The default visual style is inspired by Mantine's clean table UI, but Mantine is not required.
Installation
npm install bimatter-react-tableBasic Usage
import {
BimatterTable,
type BimatterTableColumn,
type BimatterTableRowId,
} from "bimatter-react-table";
import "bimatter-react-table/style.css";
import { useState } from "react";
type FileRecord = {
id: number;
name: string;
owner: string;
status: "Ready" | "Review" | "Draft";
size: number;
triangles: number;
updatedAt: string;
};
const columns: BimatterTableColumn<FileRecord>[] = [
{
key: "name",
title: "File",
width: 320,
minWidth: 180,
},
{
key: "owner",
title: "Owner",
width: 220,
},
{
key: "status",
title: "Status",
width: 140,
},
{
key: "size",
title: "Size",
type: "fileSize",
unit: "MB",
calc: true,
width: 140,
},
{
key: "triangles",
title: "Triangles",
type: "number",
unit: "pcs",
calc: true,
width: 180,
},
{
key: "updatedAt",
title: "Updated",
type: "date",
width: 160,
},
];
const records: FileRecord[] = [
{
id: 1,
name: "Architecture.ifc",
owner: "Anna Lebedeva",
status: "Ready",
size: 42.8,
triangles: 1240033,
updatedAt: "2026-07-09",
},
];
export function FilesTable() {
const [selectedRowIds, setSelectedRowIds] = useState<BimatterTableRowId[]>(
[],
);
return (
<BimatterTable
columns={columns}
records={records}
resizable
sortable
selectable
selectedRowIds={selectedRowIds}
highlightOnHover
onSelectionChange={({ selectedRowIds }) =>
setSelectedRowIds(selectedRowIds)
}
rowClickHandle={({ record, event }) =>
console.log("Row click", record, event)
}
/>
);
}BimatterTable
<BimatterTable columns={columns} records={records} />Props
| Prop | Type | Default | Description |
| ----------------------- | ----------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| columns | BimatterTableColumn<TRecord>[] | required | Column definitions. |
| records | TRecord[] | [] | Table records. |
| recordsm | TRecord[] | [] | Compatibility alias for records. If records is provided, it takes priority. |
| resizable | boolean | false | Enables column width resizing. |
| resisable | boolean | false | Compatibility alias for resizable. |
| sortable | boolean | false | Enables column sorting controls in the header. |
| movableColumns | boolean | false | Enables drag and drop column reordering. |
| movableRows | boolean | false | Enables drag and drop row reordering. |
| fixFirstColumn | boolean | false | Keeps the first visible column sticky on the left. The fixed first column cannot be dragged. |
| selectable | boolean | false | Enables row selection. |
| useSelectionCheckBox | boolean | false | Adds a checkbox column at the start of each row and enables checkbox-based selection. |
| selectedRowIds | BimatterTableRowId[] | undefined | Controlled selected row ids. |
| defaultSelectedRowIds | BimatterTableRowId[] | [] | Initial selected row ids for uncontrolled selection. |
| getRowId | (rowData, rowIndex) => string \| number | rowData.id ?? rowIndex | Returns a stable row id. |
| onSelectionChange | (data) => void | undefined | Called when row selection changes. |
| onRowClick | (rowData, event, rowIndex) => void | undefined | Called on row click. |
| onRowDoubleClick | (rowData, event, rowIndex) => void | undefined | Called on row double click. |
| onContextMenu | (rowData, event, rowIndex) => void | undefined | Called on row context menu. |
| onGroupRowClick | (groupData, event) => unknown | undefined | Called when a grouped row is clicked. Receives { name, children } and the click event. |
| rowClickHandle | ({ record, event, rowIndex, rowId }) => void | undefined | Object-style row click handler. clickHandle is also supported as an alias. |
| rowDoubleClickHandle | ({ record, event, rowIndex, rowId }) => void | undefined | Object-style row double click handler. doubleClickHandle is also supported as an alias. |
| contextMenuHandle | ({ record, event, rowIndex, rowId }) => void | undefined | Object-style row context menu handler. |
| useDragAndDropRows | (fromRow, toRow) => unknown | undefined | Enables dragging one row onto another row and calls the callback with source and target records. |
| searchBy | string[] | undefined | Shows a search input above the table and searches through the provided column keys, ids, or string accessors. |
| virtualized | boolean | false | Enables row virtualization with react-virtualized for large datasets. |
| virtualizedBodyHeight | number | auto | Fixed virtualized body height in pixels. When omitted, the body height is calculated from the table container and updated on resize. |
| virtualizedRowHeight | number | 44 | Fixed row height used by the virtualized grid. |
| virtualizedOverscanRowCount | number | 8 | Number of extra rows rendered above and below the visible viewport. |
| noHeader | boolean | false | Hides the table header. Header-based controls such as sorting, resizing, and column drag handles are not visible without the header. |
| hideGrouped | boolean | false | Hides columns used for grouping from the regular column list. |
| highlightOnHover | boolean | false | Enables row hover highlighting. |
| highlightOnHoverColor | string | undefined | Overrides the row hover color. |
| rowRender | (rowData) => ReactNode | undefined | Custom row content renderer. If it returns null or undefined, the default row renderer is used. |
| columnRender | (columnData) => ReactNode | undefined | Custom header content renderer. If it returns null or undefined, the default header renderer is used. |
| cellRender | (cellData) => ReactNode | undefined | Custom cell renderer. |
| ceilRender | (ceilData) => ReactNode | undefined | Compatibility alias for cellRender. It is checked before cellRender. |
| emptyContent | ReactNode | built-in empty message | Content rendered when there are no records. |
Columns
type BimatterTableColumn<TRecord extends object = Record<string, unknown>> = {
id?: string;
key?: Extract<keyof TRecord, string> | string;
accessor?: BimatterTableColumnAccessor<TRecord>;
title?: ReactNode;
width?: number | string;
minWidth?: number;
maxWidth?: number;
groupLevel?: 1 | 2 | 3;
type?: "string" | "number" | "date" | "length" | "weight" | "fileSize";
unit?: string;
calc?: boolean;
culc?: boolean;
align?: "left" | "center" | "right";
className?: string;
headerClassName?: string;
render?: (rowData: TRecord, rowIndex: number) => ReactNode;
};Column Fields
| Field | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| id | Explicit column id. Used for sorting, drag and drop, grouping, and width state. |
| key | Record field key. Also used as the column id if id is not provided. |
| accessor | Record field key or function used to read a cell value. |
| title | Header content. |
| width | Base column width. Numbers are treated as pixels. Pixel strings such as "120px" are also used for initial width calculations. |
| minWidth | Minimum column width in pixels. |
| maxWidth | Maximum column width in pixels. |
| groupLevel | Grouping level: 1, 2, or 3. |
| type | Value type used by default formatting and calculated totals. |
| unit | Unit appended to numeric and dimension-like values. |
| calc | Enables totals for numeric columns. |
| culc | Compatibility alias for calc. |
| align | Cell alignment. Defaults to left for every column type. |
| className | Class name applied to body, group, and footer cells for this column. |
| headerClassName | Class name applied to the header cell. |
| render | Column-level cell renderer. It has priority over cellRender and ceilRender. |
Accessors
type BimatterTableColumnAccessor<TRecord extends object> =
| Extract<keyof TRecord, string>
| string
| ((rowData: TRecord, rowIndex: number) => unknown);If accessor is not provided, the component reads the value by column.key,
then by column.id.
Value Types
| type | Default formatting | Default unit behavior |
| ---------- | ----------------------------- | ----------------------- |
| string | Default React value rendering | none |
| number | Intl.NumberFormat | none |
| date | Intl.DateTimeFormat | none |
| length | Formatted number + unit | built-in length unit |
| weight | Formatted number + unit | built-in weight unit |
| fileSize | Formatted number + unit | built-in file-size unit |
Override the unit with unit:
{
key: "triangles",
title: "Triangles",
type: "number",
unit: "pcs",
calc: true,
}Sorting
<BimatterTable columns={columns} records={records} sortable />When sortable is enabled, each header cell gets a sort control with three
states:
| State | Behavior |
| --------- | ------------------- |
| default | No sorting. |
| asc | Ascending sorting. |
| desc | Descending sorting. |
Click order: default -> asc -> desc -> default.
Row Selection
const [selectedRowIds, setSelectedRowIds] = useState<BimatterTableRowId[]>([]);
<BimatterTable
columns={columns}
records={records}
selectable
useSelectionCheckBox
selectedRowIds={selectedRowIds}
onSelectionChange={({ selectedRowIds }) =>
setSelectedRowIds(selectedRowIds)
}
/>;Selection behavior:
- click a row to toggle it when
selectableis enabled withoutuseSelectionCheckBox; - click a row checkbox to toggle it when
useSelectionCheckBoxis enabled; - click the table container parent itself to clear selection;
- use
Shift + clickto select the range between the last selected row and the clicked row or checkbox; - browser text selection is prevented during
Shift + click.
Selection Change Data
type BimatterTableSelectionChangeData<TRecord extends object> = {
selectedRowIds: BimatterTableRowId[];
selectedRows: TRecord[];
changedRow?: TRecord;
changedRowId?: BimatterTableRowId;
changedRows?: TRecord[];
changedRowIds?: BimatterTableRowId[];
reason?: "toggle" | "range" | "clear";
};Row Events
type BimatterTableRowEventData<TRecord extends object> = {
record: TRecord;
rowData: TRecord;
event: MouseEvent<HTMLTableRowElement>;
rowIndex: number;
rowId: BimatterTableRowId;
};
type BimatterTableRowEventHandler<TRecord extends object> = (
data: BimatterTableRowEventData<TRecord>,
) => void;
type BimatterTableRowHandler<TRecord extends object> = (
rowData: TRecord,
event: MouseEvent<HTMLTableRowElement>,
rowIndex: number,
) => void;<BimatterTable
columns={columns}
records={records}
rowClickHandle={({ record, event }) => {
console.log("click", record, event);
}}
rowDoubleClickHandle={({ record, event }) => {
console.log("double click", record, event);
}}
contextMenuHandle={({ record, event }) => {
event.preventDefault();
console.log("context menu", record);
}}
/>The legacy positional handlers onRowClick, onRowDoubleClick, and
onContextMenu are also supported.
Column and Row Reordering
<BimatterTable columns={columns} records={records} movableColumns movableRows />movableColumns enables drag and drop column reordering from the column title
area. When fixFirstColumn is enabled, the first visible column cannot be
dragged, and other columns cannot be dropped onto it.
movableRows adds a row drag handle. Dropping a row changes the visible row
order and clears active sorting so the manual order is visible.
For row-to-row drops controlled by your app, pass useDragAndDropRows:
<BimatterTable
columns={columns}
records={records}
useDragAndDropRows={(fromRow, toRow) => {
console.log("drop", fromRow, "onto", toRow);
}}
/>Search
Pass searchBy with column keys, ids, or string accessors to show the search
input above the table:
<BimatterTable
columns={columns}
records={records}
searchBy={["name", "owner", "status"]}
/>Typing in the input scrolls the table to the first match. The up and down
buttons move to the previous or next match. With virtualized, navigation uses
the virtualized grid scroll API.
Virtualization
For large datasets, enable row virtualization:
<BimatterTable
columns={columns}
records={records}
virtualized
virtualizedRowHeight={44}
/>Virtualization is implemented with react-virtualized. It virtualizes rows
while keeping BimatterTable sorting, grouping, selection, resizing, totals,
custom row rendering, and row event handlers. Columns are not virtualized
because table column counts are usually small and fixed column widths need to
stay aligned with the header and footer.
By default, the virtualized body height is calculated from the table container
height and updates on resize. Pass virtualizedBodyHeight only when you need a
fixed body height.
Grouping
Grouping is configured on columns with groupLevel.
const columns: BimatterTableColumn<FileRecord>[] = [
{
key: "status",
title: "Status",
groupLevel: 1,
},
{
key: "owner",
title: "Owner",
groupLevel: 2,
},
];Supported group levels are 1, 2, and 3. The table renders a tree-like row
structure. Groups can be expanded and collapsed.
Use hideGrouped to hide grouped columns from the regular column list:
<BimatterTable columns={columns} records={records} hideGrouped />Grouped rows can be handled with onGroupRowClick:
<BimatterTable
columns={columns}
records={records}
onGroupRowClick={(group, event) => {
console.log(group.name, group.children, event);
}}
/>type BimatterTableGroupRowData<TRecord extends object = Record<string, unknown>> = {
name: string;
children: Array<BimatterTableGroupRowData<TRecord> | TRecord>;
};name matches the visible group label, for example "Status : Ready".
children contains nested group data when another group level exists, otherwise
it contains the source records for that group.
If grouped data contains calculated columns, group subtotals are rendered in the group row itself, aligned with the calculated columns. With multiple grouping levels, each level gets its own subtotal.
Totals
Set calc: true on a numeric column to calculate totals.
{
key: "size",
title: "Size",
type: "fileSize",
unit: "MB",
calc: true,
}Totals are supported for:
number;length;weight;fileSize.
The total row is rendered as a sticky footer. When grouping is active, group subtotals are shown in each group row above the calculated columns.
Custom Rendering
All render props are optional. If a renderer is not provided, or returns null
or undefined, the table falls back to the default renderer.
column.render
Column-level cell renderer. It has the highest priority for body cells.
{
key: "status",
title: "Status",
render: (rowData) => <StatusBadge status={rowData.status} />,
}cellRender
Global cell renderer.
<BimatterTable
columns={columns}
records={records}
cellRender={({ columnData, value }) => {
if (columnData.key === "updatedAt" && typeof value === "string") {
return (
<time dateTime={value}>
{new Date(value).toLocaleDateString()}
</time>
);
}
return null;
}}
/>Cell render data:
type BimatterTableCellRenderData<TRecord extends object> = {
rowData: TRecord;
columnData: BimatterTableColumn<TRecord>;
value: unknown;
rowIndex: number;
columnIndex: number;
selected: boolean;
};ceilRender is also supported as a compatibility alias. If both ceilRender
and cellRender are provided, ceilRender is checked first.
columnRender
Custom header content renderer.
<BimatterTable
columns={columns}
records={records}
columnRender={({ columnData }) => <span>{columnData.title}</span>}
/>Column render data:
type BimatterTableColumnRenderData<TRecord extends object> = {
columnData: BimatterTableColumn<TRecord>;
columnIndex: number;
};rowRender
Custom row content renderer. The returned ReactNode is rendered inside the
table <tr>, so it should usually return one or more <td> elements.
When virtualized is enabled, rowRender is still supported. Returned <td>
and <th> elements are converted to virtualized row cells with the configured
column widths. If you return a custom component instead, it is rendered as the
content area of the virtualized row.
<BimatterTable
columns={columns}
records={records}
rowRender={({ cells }) =>
cells.map((cell) => (
<td key={String(cell.columnData.key)}>{String(cell.value)}</td>
))
}
/>Row render data:
type BimatterTableRowRenderData<TRecord extends object> = {
rowData: TRecord;
rowId: BimatterTableRowId;
rowIndex: number;
selected: boolean;
columns: BimatterTableColumn<TRecord>[];
cells: BimatterTableCellRenderData<TRecord>[];
};Fixed First Column
<BimatterTable columns={columns} records={records} fixFirstColumn />The first visible column becomes sticky on the left. If column reordering is enabled, the first visible column remains locked.
Empty State
<BimatterTable columns={columns} records={[]} emptyContent="No files found" />Styling
Import the library stylesheet once in your app entry point or root layout:
import "bimatter-react-table/style.css";BimatterTable exposes design tokens through CSS variables on the root
.bimatter-table class.
.my-table {
--bimatter-table-hover-bg: #edf2ff;
--bimatter-table-selected-bg: #e7f5ff;
--bimatter-table-border: #dee2e6;
--bimatter-table-radius: 8px;
}<BimatterTable className="my-table" columns={columns} records={records} />Common CSS variables:
| Variable | Description |
| ------------------------------------- | ------------------------------ |
| --bimatter-table-bg | Table background. |
| --bimatter-table-text | Main text color. |
| --bimatter-table-border | Border color. |
| --bimatter-table-header-bg | Header background. |
| --bimatter-table-hover-bg | Row hover background. |
| --bimatter-table-selected-bg | Selected row background. |
| --bimatter-table-selected-hover-bg | Selected row hover background. |
| --bimatter-table-resize-hover-color | Resize handle color on hover. |
| --bimatter-table-sort-active-color | Active sort icon color. |
| --bimatter-table-cell-padding-y | Vertical cell padding. |
| --bimatter-table-cell-padding-x | Horizontal cell padding. |
| --bimatter-table-font-size | Table font size. |
Public Exports
import {
BimatterTable,
BimatterTableDefault,
type BimatterTableAlign,
type BimatterTableCellRenderData,
type BimatterTableColumn,
type BimatterTableColumnAccessor,
type BimatterTableColumnRenderData,
type BimatterTableColumnType,
type BimatterTableGroupRowClickHandler,
type BimatterTableGroupRowData,
type BimatterTableGroupLevel,
type BimatterTableProps,
type BimatterTableRowEventData,
type BimatterTableRowEventHandler,
type BimatterTableRowHandler,
type BimatterTableRowId,
type BimatterTableRowRenderData,
type BimatterTableRowsDropHandler,
type BimatterTableSelectionChangeData,
type BimatterTableSortDirection,
type BimatterTableSortState,
type BimatterTableSortViewState,
} from "bimatter-react-table";