Skip to content

Advanced Tree Data Grid

Tree Data is the grid pattern for hierarchical records that still need spreadsheet-grade behavior. A tree table should not force users to choose between hierarchy and data-grid features: they still need virtual scrolling, column resizing, sorting, filtering, row selection, editing, drag-and-drop, and controlled state.

Core TypeScript tree example

Tree-like rows do not require Pro when your application only needs a visual hierarchy. The existing TypeScript core tree demo flattens parent-child records into display order, adds a level field, and renders indentation in a normal cell template.

See TypeScript Tree Data for the maintained core example source and framework notes. Use this core pattern for read-only outlines, category lists, document sections, or cases where your application owns expand/collapse outside the grid. Use the Pro plugin when the tree itself needs grid-native controls and behavior.

Why flat tree data works better

Deeply nested arrays are convenient for simple menus, but they become expensive in an editable data grid. Real applications need stable row IDs, row-level permissions, server updates, filtering, sorting, drag-and-drop, and partial refreshes. A flat tree model keeps each row addressable:

ts
type WorkItem = {
  id: string;
  parentId: string | null;
  name: string;
  owner: string;
  status: 'Open' | 'Blocked' | 'Done';
};

const source: WorkItem[] = [
  { id: 'launch', parentId: null, name: 'Launch plan', owner: 'Maya', status: 'Open' },
  { id: 'design', parentId: 'launch', name: 'Design review', owner: 'Ivo', status: 'Done' },
  { id: 'docs', parentId: 'launch', name: 'Docs update', owner: 'Nia', status: 'Blocked' },
];

The hierarchy is defined by id and parentId, while the grid still receives a normal row array.

Core setup

The Pro setup is the same across frameworks: register the plugins, mark the hierarchy column, pass a tree config, and keep the row source flat. React, Vue, Angular, Svelte, Stencil, and TypeScript examples differ only in how these objects are bound to the grid.

ts
import {
  TreeDataPlugin,
  RowOrderPlugin,
  RowSelectPlugin,
  StickyCellsPlugin,
  TREE_EXPAND_ALL_EVENT,
  TREE_COLLAPSE_ALL_EVENT,
} from '@revolist/revogrid-pro';

const columns = [
  {
    prop: 'name',
    name: 'Task',
    size: 320,
    tree: true,
    rowSelect: true,
    rowDrag: true,
    sortable: true,
  },
  { prop: 'owner', name: 'Owner', size: 140 },
  { prop: 'status', name: 'Status', size: 140 },
];

const plugins = [
  TreeDataPlugin,
  RowOrderPlugin,
  RowSelectPlugin,
  StickyCellsPlugin,
];

const tree = {
  idField: 'id',
  parentIdField: 'parentId',
  rootParentId: null,
  expandedRowIds: new Set(['launch']),
  stickyParents: true,
};

Framework wrappers pass these values as props or bindings. Plain TypeScript assigns them to the revo-grid element:

ts
grid.columns = columns;
grid.source = source;
grid.plugins = plugins;
grid.tree = tree;

Expansion state

Use expandedRowIds when expansion should be restored from a route, local storage, or backend preference. Use expandAll: true for documentation, demos, or small trees where every branch should start open.

Toolbar actions can dispatch the public tree events:

ts
grid.dispatchEvent(new CustomEvent(TREE_EXPAND_ALL_EVENT));
grid.dispatchEvent(new CustomEvent(TREE_COLLAPSE_ALL_EVENT));

What the Pro plugin adds

RevoGrid Pro Tree Data is more than indentation. It is designed for advanced data grid tree structures:

CapabilityWhy it matters
Flat source rowsStable IDs, simple backend contracts, fast updates
tree: true columnConsistent expand controls and indentation in the data grid
Computed tree metadataThe original row objects stay clean
Expand and collapse eventsExternal toolbars and saved state are straightforward
Row order integrationDragging rows can update hierarchy relationships
Row selection integrationParent and descendant selection can be coordinated
Sticky parent rowsUsers keep branch context while scrolling long trees
Virtual renderingLarge hierarchies remain practical

Tree Data vs row grouping

Tree Data and row grouping can both create expandable-looking sections, but they solve different data problems.

Use Tree Data when parent-child relationships are part of the records themselves. A row has a stable id, a parentId, and a real position in a hierarchy: departments contain teams, folders contain files, projects contain tasks, bill-of-material rows contain child parts. Moving a row can change its parent. Expanding a parent shows its actual descendants.

Use row grouping when the source rows are still flat records and the hierarchy is only a view created from shared values. For example, orders can be grouped by country, then city, then status. The group headers are generated summaries, not source rows with IDs. Changing the grouping props changes the view, but it does not change each order's parent.

Use caseTree DataRow Grouping
Data modelExplicit parent-child recordsFlat rows grouped by field values
Parent rowsReal rows from your sourceGenerated group headers
IdentityStable id and parentIdGroup keys from values such as status
Best forOrg charts, folders, tasks, BOMsReports, category breakdowns, grouped lists
ReorderingCan mean reparenting a rowUsually changes row order inside a grouped view
ConfigurationTree column plus tree configgrouping = { props: [...] }

If a row belongs under one specific parent even when sorting or filtering changes, use Tree Data. If rows are being arranged temporarily by common values for analysis, use Row Grouping.

Design recommendations

Use Tree Data when hierarchy is part of the record identity. Use row grouping when hierarchy is only a view over flat categories. Use master-detail rows when each parent opens a separate detail surface rather than a real branch of rows.

For production trees, keep the first tree column wide enough for indentation and labels, place status or numeric columns to the right, and avoid expensive templates in every tree cell. If the hierarchy can be very deep, set a clear maximum supported depth in your product UI and validate imported data before it reaches the grid.

Framework examples