rx-datatable manual
rx-datatable is a stack of React list/tree/table widgets:
| Component | Adds | Core variant (no wrapper element) |
|---|---|---|
ListBox |
selection, multiselect, virtualization | ListCore |
TreeList |
nesting, expand/collapse | TreeListCore |
DataTable |
columns, headings, sorting, resizing | DataTableCore |
This manual shows the most common usage patterns. For every export, prop and type, see the
API reference, which TypeDoc generates from the documentation comments in the
source — DataTableProps, DataTableColumn, DataTableCellConfig, TreeListMainProps and
ListCoreProps are the ones worth reading in full. The source itself lives at
github.com/qwertie/rx-datatable, and also ships
inside the npm package under src/.
Installation
npm install rx-datatable
import 'rx-datatable/datatable.css'; // once, anywhere in your app
import { DataTable } from 'rx-datatable';
Import the stylesheet yourself; the package entry point deliberately does not import it for
you, because a CSS import there would make the package unloadable by anything without a
CSS-aware bundler — plain node, server-side rendering, or your own test runner. Skip it and
the tables render unstyled.
All rules in datatable.css use stable class names prefixed dtbl- and read --dtbl-* CSS
variables for colors and spacing.
A plain table (single row type)
The only required props are columns and list. By default, a column named n shows
row[n], and clicking a heading sorts by that value. Give a column a numeric width to make
it user-resizable.
type Planet = { name: string, diameter: number, moons: number };
<DataTable<Planet>
list={planets}
selMode="multi" // Ctrl+click / Shift+click multiselect
columns={[
{ name: 'name', heading: 'Planet', width: 120 },
{
name: 'diameter', heading: 'Diameter', width: 110,
// getValue/renderCell/RenderCell customize a column at increasing depth
renderCell: (node, props) => `${(props.value as number).toLocaleString()} km`,
},
{ name: 'moons', heading: 'Moons', width: 80 },
]}/>
Useful column props: getValue (derive the value), getSortableValue and compare
(customize sorting; use compare: compareByLocale for text), noSort, noResize,
maxWidth, headingProps, cellProps, getColSpan.
A tree grid
Provide getChildren or getParents, and set isTreeCol: true on exactly one column.
getParents returns the path of parents (root first) for each item in list; parents that
appear in several paths must be distinct values (see getParentPathsOf).
<DataTable<FileInfo|string>
list={files}
columns={columns} // one column has isTreeCol: true
getParents={f => getParentPathsOf((f as FileInfo).folderPath, '/', true)}
/>
If expansion state or selection seems to reset when the surrounding component re-renders,
provide getSelKey so items are identified by a stable key rather than by object identity.
Multiple row types
A table has one set of columns, but each row type can configure how cells get their values
and how they render, via getRowConfig. In this example, folders are plain strings while
files are FileInfo objects; folder rows span all 3 columns and display only the last path
segment:
const folderCells = new Map<string, DataTableCellConfig<string|FileInfo, string>>([
['name', {
getValue: path => path.substring(path.lastIndexOf('/') + 1),
getColSpan: _ => 3,
}],
]);
<DataTable<FileInfo|string>
list={files}
columns={columns}
getParents={f => getParentPathsOf((f as FileInfo).folderPath, '/', true)}
getRowConfig={node => typeof node.item === 'string' ? { columns: folderCells } : undefined}
/>
App-wide configuration: TableConfig
So that individual call sites don't repeat themselves, provide icons and a default theme once near your app root. Nested providers merge with (and override) outer ones.
import { TableConfig, darkTheme } from 'rx-datatable';
<TableConfig
theme={darkTheme}
icons={{ sortAscending: <ChevronUp/>, sortDescending: <ChevronDown/>,
expanded: <ChevronDown/>, collapsed: <ChevronRight/> }}
renderIconButton={props => <IconButton size="large" {...props}/>}>
<App/>
</TableConfig>
Defaults: small dependency-free inline SVG chevrons and a plain round-hover <button>.
Theming
A TableTheme is a typed bundle of the --dtbl-* CSS variables, split into two independent
halves that combine with an object spread:
TableColors— the color scheme: row/header backgrounds, text, borders, selection and hover colors, resize cursor, andcolorScheme(the CSScolor-schemeof the container). Presets come in three families — plain (lightColors, the default look with green selection on white, anddarkColors), gray (lightGrayColors: gray header band, blue selection;darkGrayColors) and amber (lightAmberColors: cream rows;darkAmberColors) — plus anautovariant of each (autoColors,autoGrayColors,autoAmberColors) that follows the browser's light/dark preference with no JavaScript, using CSSlight-dark()(all major browsers since mid-2024).TableMetrics— everything else:cellPadding,itemPadding,treeIndent(pixels per tree level) andstripeBackground(a tint over alternating table rows; translucent values work over any color scheme). Presets:defaultMetrics,compactMetrics, and the add-onstripedRows, e.g.{ ...darkTheme, ...stripedRows }.
Complete themes combining the two are also exported: lightTheme (equals the un-themed
default), darkTheme, autoTheme, lightGrayTheme, darkGrayTheme, autoGrayTheme,
lightAmberTheme, darkAmberTheme, autoAmberTheme. Variables are applied to the
component's outer element — never globally — so differently-themed tables coexist freely:
import { darkTheme, darkColors, compactMetrics, TableTheme } from 'rx-datatable';
const compactDark: TableTheme = { ...darkColors, ...compactMetrics };
<DataTable theme={compactDark} .../> // per table
<TableConfig theme={darkTheme}>...</TableConfig> // subtree default
Prefer stylesheets? Pass a string: it becomes a class on the outer element, and your CSS defines the variables:
.compact-blue { --dtbl-cell-padding: 2px 4px; --dtbl-selected-bg: #cde; }
When using the *Core variants (which render no outer element), apply
themeProps(myTheme) to your own container element instead.
Reactivity: plain React or MobX
Out of the box (the default adapter), everything works with ordinary React semantics:
clicks re-render the table, and you change data by passing new props. Collections you pass
for selection, expandedParents or columnsToSortBy can be the package's WatchableSet /
WatchableArray, which the components subscribe to, so you can also mutate them from outside
the table.
If your app uses MobX, install the MobX adapter once at startup:
import { setReactivityAdapter } from 'rx-datatable';
import { mobxReactivityAdapter } from 'rx-datatable/mobx'; // needs mobx + mobx-react
setReactivityAdapter(mobxReactivityAdapter);
Every component then behaves as a mobx-react observer: rows re-render when observable row
data mutates, and selection/expandedParents/columnsToSortBy can be observable.set /
observable.array. You can also implement ReactivityAdapter for another library; its five
members (observer, runInAction, observableSet, observableArray, computed) are named
after the MobX APIs they abstract.
Virtualization
For long lists, pass virtualize={{ height }} (a number, or a function of the item; see
VirtualizationSettings). Groups of off-screen rows render as placeholder gaps that
materialize as they approach the viewport.
Once loaded, rows do not unload so that the browser's search function (Ctrl+F) can search
over any rows that have been viewed. Virtualization isn't designed for scalability; generally
rendering is O(N log N) for N rows. Instead, this feature exists because browsers inherently
render tables slowly, and we just needed it not to be quite so slow.
<DataTable list={hugeList} columns={columns} virtualize={{ height: 32 }}/>
Odds and ends
readOnlyprop, or aReadOnlyContext.Provider, grays out rows and disables interaction.placeholderrenders when the list is empty.headerRows/footerRowsadd extra configurable rows;noHeader,noStructureWrapandDataTableCoresupport advanced table layouts.Draggable(the primitive behind column resizing) is exported and reusable for any "drag to change a number" UI, e.g. split panes.