API REFERENCE · DATA DISPLAY
<dj-data-grid>
A virtualized, sortable, selectable data grid built on TanStack Table and TanStack Virtual.
Give it columns, data, and a height. The core covers columns, in-memory data, sorting, virtual rows, row selection, keyboard row navigation, and calculated columns (GridColumn.compute). Everything else is a plugin. The grid has ARIA role grid.
npm install @dojo-ng/data-grid
import "@dojo-ng/data-grid";
Plugins
- Filtering, pagination, inline editing, tree rows, grouping, CSV export, and master-detail are plugins. Pass an array of plugin objects to the
pluginsproperty, from JavaScript only. - A recommended order: one structural plugin first (
treePluginorgroupsPlugin, never both), theneditPlugin,cellComponentsPlugin, andformatsPlugin, then the plugins that only add controls (filterPlugin,paginationPlugin,exportPlugin,detailPlugin). - Changing
pluginsrebuilds the table, so set it once, early.
data-grid-cell-componentsIn-row Lit components (per-column render + actionButton/checkmark helpers)data-grid-detailMaster-detail (expandable row detail / subgrid)data-grid-editControlled inline cell editingdata-grid-exportCSV exportdata-grid-filterQuick + per-column filteringdata-grid-formatsValue-formatting plugin (Intl number/currency/percent/date via @dojo-ng/i18n)data-grid-groupsRow grouping + aggregatesdata-grid-paginationPagination (reuses dj-pagination)data-grid-rowstateRow/cell state styling plugin (conditional row parts + cell emphasis)data-grid-selectCheckbox selection column plugin (select-all, range selection)data-grid-treeTree (hierarchical) rows
Opening rows: activation
activation decides what a plain click or Enter means on a row.
"none"(the default): click, Space, and Enter all toggle selection."click"(the mail and preview-pane idiom) or"double"(the file-manager idiom): a plain click, or a double click, opens the row and emitsdj-activatewith{ row, index }, whererowis the original row data. Selection does not change.- With activation on, Enter opens the row and Space selects it.
- Modifier clicks always select and never open: Ctrl or Cmd-click toggles a row, and Shift-click selects a range.
"double"uses the browser's owndblclick, so the two clicks inside a double click never open the row on their own.- Activation works with any
selection-mode, including"none", so a read-only list can have clickable rows. - To open rows by clicking while the user also builds a set for bulk actions, combine
activation="click",selection-mode="multiple", and the checkbox column from@dojo-ng/data-grid-select.
Rendered rows: dj-range-change
dj-range-changefires when the window of rendered rows moves, so you can load data in and out, or load more at the end of the list.- The detail is
{ start, end, count, rendered }: the first and last rendered row index (inclusive), the total number of rows, and the list of rendered indexes. - The range includes the 8 extra rows the grid renders beyond each edge of the viewport. It is what the grid has rendered, not what the user can see, so fetching this range never leaves a gap.
- To load more at the end:
if (e.detail.end >= e.detail.count - 1) loadMore(). - When nothing is rendered,
startandendare -1 andcountis the real count. - The event fires after rendering and only when
(start, end, count)changes, so settingdatain the handler is safe.
Printing
- When the page is printed, every row becomes part of a real
<table>with a<thead>, and browsers repeat the header on each printed page. This does not apply to rows drawn by the detail plugin (@dojo-ng/data-grid-detail). - Safari does not repeat the table header on each printed page. This is a WebKit limitation with no reliable CSS fix.
Not supported yet
- A data set larger than
data: the scrollbar is sized fromdata.length, so it cannot include rows that are not loaded.
Need one of these? Make a request on Discord or add an issue (work item) on Heptapod.
Properties
| Property | Attribute | Type | Default |
|---|---|---|---|
columns |
columns |
GridColumn[] |
[] |
data |
data |
Row[] |
[] |
selectionMode |
selection-mode reflected |
"none""single""multiple" |
"none" |
activationWhat a plain click / Enter on a row means. Default "none" = the original toggle behavior. |
activation reflected |
"none""click""double" |
"none" |
rowHeight |
row-height |
number |
36 |
height |
height |
string |
"20rem" |
pluginsPlugin set. Set in JavaScript (rich data). Declared up front because TanStack row models must exist at table creation; a post-mount change rebuilds the table. Default []. |
Property only | DataGridPlugin[] Plugins |
[] |
Events
| Event | Description |
|---|---|
dj-sort | |
dj-selection-change | |
dj-range-change | detail { start, end, count, rendered } — inclusive
first and last rendered row-model indices, the total row count, and the full index list; start
and end are -1 when nothing is rendered |
dj-activate | detail { row, index }, where row is
the original row data |
CSS parts
Style these with dj-data-grid::part(name).
gridheadrowcellchrome-topchrome-bottomsubheaddetail
Methods
| Method | Description |
|---|---|
toggleAt(index: number) | |
activateAt(index: number) | Emit dj-activate for a row-model index. Fires regardless of selectionMode (a read-only list with clickable rows is a real case) but never under activation="none". |
Types
The types that the properties above use, as they are declared in the source.
GridColumn
export interface GridColumn {
id: string;
header?: string;
accessorKey?: string;
sortable?: boolean;
width?: string;
/** Derive the cell value from the whole row (calculated columns). Maps to a TanStack
* `accessorFn`. A row total is `compute: r => r.a + r.b`; no plugin needed. */
compute?: (row: Row) => unknown;
/** TanStack aggregation for this column when grouping (set by the groups plugin's columns()
* hook; copied onto the ColumnDef). Core never sets it itself. */
aggregationFn?: AggregationFnOption<Row>;
}
Row
export type Row = Record<string, unknown>;