Getting Started
Your first TreeListView, in about five minutes.
1. Add the runtime
Copy the runtime file into your application and load it with a plain script tag. There is no build step, no framework, and no package to install.
<script src="./bm-treelistview.min.js"></script>
Which file you use depends on where the page lives:
| File | Use it for | Limits |
|---|---|---|
bm-treelistview.min.js |
your licensed application | none |
bm-treelistview.demo.js |
the public marketing site and playground | 500 loaded nodes, demo badge |
Do not upload the commercial runtime to a public location. See Demo and commercial builds.
2. Put the element on the page
<div class="grid-wrapper">
<bm-treelistview id="grid"></bm-treelistview>
</div>
<style>
/* The component is display:block with height:100%, so it fills its parent.
Give the parent a height, or set one on the element directly. */
.grid-wrapper { height: 520px; }
</style>
The most common first problem is a grid with no height. If you see a thin sliver instead of a grid, this is why.
3. Give it data
<script>
customElements.whenDefined('bm-treelistview').then(async () => {
const grid = document.getElementById('grid');
await grid.ready();
const columns = [
{ id: 'name', title: 'Project', tree: true, width: '280px' },
{ id: 'owner', title: 'Owner', width: '160px' },
{ id: 'budget', title: 'Budget', kind: 'currency', align: 'right', width: '140px' },
{ id: 'due', title: 'Due', kind: 'date', width: '140px' },
{ id: 'done', title: 'Done', kind: 'boolean', align: 'center', width: '90px' },
];
const items = [
{
id: 'p1',
cells: { name: 'Platform', owner: 'Priya', budget: 120000, due: '2026-03-01', done: false },
expanded: true,
children: [
{ id: 'p1-a', cells: { name: 'API', owner: 'Ade', budget: 45000, due: '2026-01-15', done: true } },
{ id: 'p1-b', cells: { name: 'Auth', owner: 'Bo', budget: 38000, due: '2026-04-02', done: false } },
],
},
{ id: 'p2', cells: { name: 'Mobile', owner: 'Chen', budget: 91000, due: '2026-06-30', done: false } },
];
await grid.setData(columns, items);
});
</script>
That is a working grid: expandable, sortable, keyboard navigable and screen-reader accessible.
Three things worth knowing immediately
ready()
The element is defined asynchronously. Calling setData() on an element you created a millisecond ago
will fail. Always await ready() first.
await customElements.whenDefined('bm-treelistview');
await grid.ready();
Stencil's own componentOnReady() is not available in the runtime you ship — it belongs to the lazy
build, not the single-file one. ready() works in both.
Column kind decides more than looks
{ id: 'budget', title: 'Budget', kind: 'currency' }
kind: 'currency' formats the value and makes the column sort numerically and offer numeric filter
operators. A currency column left as plain text sorts 100, 1000, 25, 3000. Pick the right kind and
everything downstream follows.
cells should be keyed by column id
cells: { name: 'Platform', owner: 'Priya' } // ✅ order-independent
cells: ['Platform', 'Priya'] // ⚠️ positional
Both work. The keyed form survives someone reordering your column definitions; the positional form does not, and fails silently when it breaks.
Turning features on
Everything below defaults to off, so a grid only carries what you ask for.
<bm-treelistview
id="grid"
mode="tree" <!-- or "list" -->
theme="auto" <!-- follows the OS light/dark setting -->
density="comfortable" <!-- compact | spacious -->
zebra="true"
selection-mode="checkbox" <!-- none | single | multiple | checkbox -->
show-filter="true" <!-- global search box -->
show-column-filters="true" <!-- per-column filter row -->
show-footer="true" <!-- row and selection counts -->
show-totals="true" <!-- totals row from column aggregates -->
show-toolbar="true" <!-- exposes the toolbar slots -->
show-column-menu="true" <!-- column chooser -->
allow-column-reorder="true" <!-- drag headers -->
allow-grouping="true"
show-group-panel="true" <!-- drop a header here to group -->
allow-drag-drop="true" <!-- drag rows -->
editable="true" <!-- with per-column editable: true -->
virtualize-threshold="200" <!-- virtualise automatically above 200 rows -->
row-height="38"
persist-state="true"
state-storage-key="projects-grid"
></bm-treelistview>
Reacting to the user
grid.addEventListener('tlvSelectionChange', event => {
// single mode: a node. Every other mode: an array.
showDetail(event.detail);
});
grid.addEventListener('tlvRowDoubleClick', event => openProject(event.detail.id));
grid.addEventListener('tlvContextMenu', event => {
const { node, x, y } = event.detail;
showMyMenu(node, x, y);
});
Every event bubbles and is composed, so you can listen once on a container rather than on each grid.
A note on the demo runtime
If you are working against bm-treelistview.demo.js, the console will carry a one-off notice and the
grid caps at 500 loaded nodes with a banner explaining why. That is the demo build behaving correctly,
not a bug.
Next
- API guide — every property, method and event
- Integration guide — React, Angular, Vue, Svelte, Blazor
- Recipes — worked solutions to common requirements
- Theming — CSS variables and parts
- Performance — large datasets