Tree grid
Expandable branches with lazy children, path search, filters that keep the tree's shape, detail rows and checkboxes.
8 demos
Expandable branches with lazy children, path search, filters that keep the tree's shape, detail rows and checkboxes.
Checking a folder checks what is under it, partly checked folders show it, and children load lazily from a source that also works from disk.
A document library with more columns than a laptop holds: the tree and owner pin left, the status pins right, the description takes the spare width and the least important columns hide first.
An organisation chart with many columns, a file system with deep folders, and nested groups that start collapsed, with the tree toolbar, column fitting and auto height.
A regional inventory whose detail rows are trees of their own, with their own labels and time zone.
Pipelines as group rows above their latest runs, beside the flat runs grid and its filter builder on the same page.
A page plugin adds a cell renderer, a toolbar button, a menu entry and event hooks; the same plugin serves a grid.
The page registers its own export writers; the tree runs them on its visible, selected or all rows.
Build one like it
The smallest complete version, in each language CanopyX supports for this view. The demo above is the same API with more data.
using CanopyX;
using CanopyX.Html;
var files = new CanopyTreeView("files", "File shares") {
Selection = CanopySelectionMode.Single,
Search = new CanopySearchOptions()
};
files.Columns.Add(new CanopyColumn("name", "Name") {
Sortable = true,
Sizing = CanopyColumnSizing.Preferred(280)
});
files.Columns.Add(new CanopyColumn("size", "Size") {
Kind = CanopyColumnKind.Number,
Sortable = true
});
files.Columns.Add(new CanopyColumn("owner", "Owner") { Sortable = true });
files.Columns.Add(new CanopyColumn("modified", "Modified") {
Kind = CanopyColumnKind.DateTime,
Sortable = true
});
var finance = new CanopyNode("finance", @"\\fs01\finance") {
Kind = "folder",
Expanded = true
};
finance.Children.Add(
Document("budget", "Budget 2026.xlsx", 482_304, "anna.kowalska", 14));
finance.Children.Add(
Document("invoices", "Invoices Q3.pdf", 2_310_144, "ben.okafor", 9));
var payroll = Document("payroll", "Payroll export.csv", 96_256, "chen.wei", 2);
payroll.Badges.Add(new CanopyBadge("Shared externally", CanopyTone.Warning));
finance.Children.Add(payroll);
var people = new CanopyNode("people", @"\\fs01\people") {
Kind = "folder",
Expanded = true
};
people.Children.Add(
Document("handbook", "Staff handbook.docx", 1_048_576, "dana.levi", 30));
people.Children.Add(
Document("rota", "On-call rota.xlsx", 40_960, "erik.lund", 1));
files.Roots.Add(finance);
files.Roots.Add(people);
files.SaveHtmlPage("files.html");
CanopyNode Document(string id, string name, long bytes, string owner, int age) {
var node = new CanopyNode(id, name) { Kind = "file" };
node.Cells["size"] = new CanopyCell(bytes) { Text = $"{bytes / 1024} KB" };
node.Cells["owner"] = owner;
var saved = new DateTime(2026, 10, 16, 9, 30, 0, DateTimeKind.Utc);
node.Cells["modified"] = saved.AddDays(-age);
return node;
}Run this snippet (tree grid, C#)Its output, generated with the site.
import { createTreeGrid } from "@evotecit/canopyx";
import "@evotecit/canopyx/styles.css";
const file = (id, label, bytes, owner, modified, badges) => ({
id, label, kind: "file", badges,
cells: {
size: { value: bytes, text: `${Math.round(bytes / 1024)} KB` },
owner,
modified
}
});
const grid = createTreeGrid(document.getElementById("files"), {
view: {
protocol: "canopyx/2", type: "view", id: "files", label: "File shares",
selection: "single",
search: { minLength: 1, maxResults: 50 },
columns: [
{ id: "name", title: "Name", kind: "text", width: 280, sortable: true },
{ id: "size", title: "Size", kind: "number", sortable: true },
{ id: "owner", title: "Owner", kind: "text", sortable: true },
{ id: "modified", title: "Modified", kind: "datetime", sortable: true }
],
source: { kind: "inline" },
roots: [
{
id: "finance", label: "\\\\fs01\\finance",
kind: "folder", expanded: true,
children: [
file("budget", "Budget 2026.xlsx", 482304, "anna.kowalska",
"2026-10-02T09:30:00Z"),
file("invoices", "Invoices Q3.pdf", 2310144, "ben.okafor",
"2026-10-07T09:30:00Z"),
file("payroll", "Payroll export.csv", 96256, "chen.wei",
"2026-10-14T09:30:00Z",
[{ text: "Shared externally", tone: "warning" }])
]
},
{
id: "people", label: "\\\\fs01\\people",
kind: "folder", expanded: true,
children: [
file("handbook", "Staff handbook.docx", 1048576, "dana.levi",
"2026-09-16T09:30:00Z"),
file("rota", "On-call rota.xlsx", 40960, "erik.lund",
"2026-10-15T09:30:00Z")
]
}
]
}
});
await grid.ready;Run this snippet (tree grid, JavaScript)Its output, generated with the site.
Try this in the demo
- Lazy branches
Expand a share, then one of its departments.
Each branch opens from one pack file in data/, read the first time you open it. The page never downloads the whole tree.
- Search shards
Type budget into Search files and folders and pick a hit.
Only the shards for the words you typed are read; the tree then opens every branch on the path to the hit.
- Filters
Turn on Only changed in the last 7 days.
Folders above a changed file stay visible, so the tree keeps its shape around the matches.
- Detail rows
Expand a file that has an arrow, then its History and a version.
The version's changes open as a before and after table inside the tree, under the file they belong to.
- Deep links
Select a row and look at the address bar of the full-screen page.
The hash carries the row's path. Opening that address expands straight to the row.