Dojo NG is being built in the open, one section at a time — follow along on Heptapod.

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.

Try it in the playground

@dojo-ng/data-grid · v0.1.2

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 plugins property, from JavaScript only.
  • A recommended order: one structural plugin first (treePlugin or groupsPlugin, never both), then editPlugin, cellComponentsPlugin, and formatsPlugin, then the plugins that only add controls (filterPlugin, paginationPlugin, exportPlugin, detailPlugin).
  • Changing plugins rebuilds the table, so set it once, early.

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 emits dj-activate with { row, index }, where row is 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 own dblclick, 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-change fires 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, start and end are -1 and count is the real count.
  • The event fires after rendering and only when (start, end, count) changes, so setting data in 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 from data.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

PropertyAttributeTypeDefault
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

EventDescription
dj-sort
dj-selection-change
dj-range-changedetail { 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-activatedetail { row, index }, where row is the original row data

CSS parts

Style these with dj-data-grid::part(name).

gridheadrowcellchrome-topchrome-bottomsubheaddetail

Methods

MethodDescription
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>;