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