npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-table

Basic 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 selectable is enabled without useSelectionCheckBox;
  • click a row checkbox to toggle it when useSelectionCheckBox is enabled;
  • click the table container parent itself to clear selection;
  • use Shift + click to 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";