npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cbx-tree

v2.1.0

Published

Web Component for building tree-like UI with checkable items

Readme

<cbx-tree>: The Checkbox Tree element

The <cbx-tree> element is a web component for building tree-like hierarchic lists with checkable items. Tree items in the <cbx-tree> element are collapsible if they have nested subtrees. Every item is equipped with a checkbox which can be in one of the following states:

  • checked: the item and all its children are checked,
  • unchecked: the item and all its children are unchecked,
  • indeterminate: the item is technically unchecked but some of its children are checked.

Demonstration of design and functionality of the <cbx-tree> element

Live demo on CodePen

Installation and import

If you use a bundler in your project, install cbx-tree as a dependency:

npm install cbx-tree

Now you may import it wherever it’s needed:

import 'cbx-tree';

If you don’t use bundlers, just import the component as a module in your HTML files:

<script type="module" src="https://unpkg.com/cbx-tree"></script>

or in ES modules:

import 'https://unpkg.com/cbx-tree';

Usage notes

There are two ways to feed initial tree data to the <cbx-tree> component.

The first way is to provide tree data directly in HTML by adding JSON content as follows:

<cbx-tree name="reading-list[]">
  <script type="application/json">
    [
      {
        "title": "Epic poetry",
        "value": "category-123",
        "icon": "/icons/epic-icon.svg",
        "children": [
          {
            "title": "Ancient Greek poems",
            "value": "category-179",
            "icon": "/icons/greek-icon.svg",
            "children": [
              {
                "title": "Iliad",
                "value": "book-10",
                "icon": "/icons/manuscript-icon.svg",
                "checked": true
              },
              {
                "title": "Odyssey",
                "value": "book-11",
                "icon": "/icons/manuscript-icon.svg",
                "checked": true
              }
            ]
          },
          {
            "title": "Ancient Mesopotamian poems",
            "value": "category-151",
            "icon": "/icons/mesopotamian-icon.svg",
            "collapsed": true,
            "children": [
              {
                "title": "Epic of Gilgamesh",
                "value": "book-8",
                "icon": "/icons/clay-tablet-icon.svg"
              }
            ]
          }
        ]
      }
    ]
  </script>
</cbx-tree>

[!NOTE] Similarly to the <textarea> content, the data you provide in HTML is only used as a default value. In other words, dynamic updates of the HTML content don’t affect the current tree. To update the tree dynamically, one should use the JavaScript API provided by the component.

The second option is to fill the initial tree programmatically using the setData() method.

HTML:

<cbx-tree name="reading-list[]"></cbx-tree>

JavaScript:

customElements.whenDefined('cbx-tree').then(() => {
  const readingList = document.querySelector('[name="reading-list[]"]');
  readingList.setData([
    {
      title: 'Epic poetry',
      value: 'category-123',
      icon: '/icons/epic-icon.svg',
      children: [
        {
          title: 'Ancient Greek poems',
          value: 'category-179',
          icon: '/icons/greek-icon.svg',
          children: [
            {
              title: 'Iliad',
              value: 'book-10',
              icon: '/icons/manuscript-icon.svg',
              checked: true,
            },
            {
              title: 'Odyssey',
              value: 'book-11',
              icon: '/icons/manuscript-icon.svg',
              checked: true,
            },
          ],
        },
        {
          title: 'Ancient Mesopotamian poems',
          value: 'category-151',
          icon: '/icons/mesopotamian-icon.svg',
          collapsed: true,
          children: [
            {
              title: 'Epic of Gilgamesh',
              value: 'book-8',
              icon: '/icons/clay-tablet-icon.svg',
            },
          ],
        },
      ],
    },
  ]);
});

[!NOTE] JavaScript API of the <cbx-tree> component becomes fully functional as soon as the element is registered and defined. To stay on the safe side, it’s worth using the whenDefined() guard as shown in the example above.

Tree data structure

As shown in the examples above, the tree is initialised with an array of objects representing the tree’s root items. Each item can have children forming a nested subtree. The table below provides information about the properties that can be specified for tree items at any nesting level.

| Property | Type | Required | Description | | ----------- | ---------------- | -------- | ----------------------------------------------------------------- | | title | string | yes | Text label of the tree item | | value | string¹ | yes | Internal value identifying the checked item in the submitted data | | icon | string | no | Item icons’s URL or SVG icon code | | checked | boolean | no | Initial state of the item selection | | collapsed | boolean | no | Whether a nested subtree is collapsed initially | | children | array or null² | no | Nested subtree items |

¹ Must be unique within the entire tree.
² The value null of the children property is used for on-demand loading of the subtree.

Attributes

This element includes the global attributes.

disabled

Applying this Boolean attribute turns all interactive controls within the tree into the disabled state. Items in the disabled tree cannot be collapsed or expanded by the user, and states of the checkboxes cannot be changed via the GUI.

name

A mandatory attribute name is used by the <cbx-tree> component to construct data to be submitted with the form. Since the widget contains multiple checkable items, it may be a good idea to use a name with square brackets appended. This notation allows some server-side frameworks treat submitted data as an array.

<cbx-tree name="reading-list[]"></cbx-tree>

nohover

By default, items in the <cbx-tree> component grab focus and get highlighted when pointer hovers over them, similarly to options in the <select> element’s dropdown. A Boolean attribute nohover makes the <cbx-tree> component deactivate this behaviour, so that items only become selected when clicked or focused by keyboard navigation (similarly to options in a <select> with the multiple attribute specified).

<cbx-tree name="reading-list[]" nohover></cbx-tree>

Instance properties

The CbxTree interface also inherits properties from its parent, HTMLElement.

Validation-related properties validity, validationMessage, and willValidate are transparently exposed from the underlying ElementInternals object which allows the <cbx-tree> element participate in form validation.

CbxTree.disabled

Reflects the value of the element’s disabled attribute.

CbxTree.form

The read-only property that references the HTMLFormElement associated with this element.

CbxTree.formData

A FormData object which contains key-value pairs of the currently checked items in the tree. Note that the element’s name attribute is used for all keys, so the FormData object represents an array of checked values. The property is read-only.

const readingList = document.querySelector('[name="reading-list[]"]');
console.log('Checked values:', readingList.formData.getAll('reading-list[]'));

CbxTree.name

Reflects the value of the element’s name attribute.

CbxTree.noHover

Reflects the value of the element’s nohover attribute.

CbxTree.subtreeProvider

The subtreeProvider property is used in cases where on-demand subtree loading is required. If your initial tree doesn’t contain data for some nested subtrees, you may define your custom function for subtree generation/fetching which will be called when the user expands the target item for the first time.

[!IMPORTANT] The items that allow on-demand loading, should have their children property set to null initially.

The custom subtree provider is a function that accepts the value of the target item as its argument and returns a promise that resolves with an array representing a subtree data for this specific item.

customElements.whenDefined('cbx-tree').then(() => {
  const readingList = document.querySelector('[name="reading-list[]"]');
  readingList.subtreeProvider = async (itemValue) => {
    const response = await fetch(`/reading-list/items/${itemValue}`);
    return (await response.json()).children;
  };
});

CbxTree.type

A read-only property provided for consistency with browser-provided form controls. The same as Element.localName.

Instance methods

The CbxTree interface also inherits methods from its parent, HTMLElement.

Validation-related methods checkValidity(), reportValidity(), and setValidity() are transparently exposed from the underlying ElementInternals object which allows the <cbx-tree> element participate in form validation.

CbxTree.filter()

This method can be used to “filter” the tree by hiding those items that don’t meet custom criteria. The method accepts a single argument, a preficate function. The predicate is passed an object argument with item’s title and value as properties, and the return value must be true if the item passes the filter and false otherwise. It should be noted that if an item passes the filter, its descendants remain visible even if they themselves don’t satisfy the filtering condition.

const readingList = document.querySelector('[name="reading-list[]"]');
const filterInput = document.getElementById('filter');
filterInput.addEventListener('input', () => {
  const query = filterInput.value.trim().toLocaleLowerCase();
  const predicate = query.length ? ({title}) => title.toLocaleLowerCase().includes(query) : () => true;
  readingList.filter(predicate);
});

CbxTree.setData()

The setData() method is used for complete overwriting and rerendering the entire tree. It accepts a single argument, a new tree data. All existing changes will be lost and replaced by the newly provided data after calling this method. See an example in the Usage notes section.

CbxTree.toggle()

Use this method for dynamic expansion or collapsing of all items in the tree. The method accepts an optional boolean argument, isExpanding, which controls whether items should be expanded (true) or collapsed (false).

const readingList = document.querySelector('[name="reading-list[]"]');
readingList.toggle(false); // collapse all

Note that this method doesn’t expand items that have on-demand loading behavior. Also, programmatic toggling doesn’t trigger the cbxtreetoggle event.

CbxTree.toggleChecked()

This method can be used to check or uncheck all the items in the tree. The method accepts an optional boolean argument, checked, which controls whether items should be checked (true) or unchecked (false).

const readingList = document.querySelector('[name="reading-list[]"]');
readingList.toggleChecked(true); // check all

Note that programmatic checking of the items doesn’t trigger the cbxtreechange event.

CbxTree.toJSON()

Returns the current state of the tree in the same format as the array used for tree initialisation. This method allows for JSON serialisation of the control state.

const readingList = document.querySelector('[name="reading-list[]"]');
console.log('Tree data:', JSON.stringify(readingList, null, 2));

Events

cbxtreechange

The cbxtreechange custom event is fired when the user changes the state of a checkbox in the tree. A complete information on the tree selection state is available as a FormData object through the detail property of the event instance.

const readingList = document.querySelector('[name="reading-list[]"]');
readingList.addEventListener('cbxtreechange', (e) => {
  const selectionData = e.detail; // FormData instance
  console.log('Selected books & categories:', ...selectionData.values());
});

cbxtreetoggle

The cbxtreetoggle custom event is fired when the user clicks a toggle button to expand or collapse a subtree under one of the tree items. The detail property of the event instance provides additional information about the target item:

| Property | Description | | ---------- | --------------------------------------------------------------- | | title | Title of the target item | | value | Value of the target item | | newState | New state of the target item (either expanded or collapsed) |

const readingList = document.querySelector('[name="reading-list[]"]');
readingList.addEventListener('cbxtreetoggle', (e) => {
  const {title, value, newState} = e.detail;
  console.log(`Item “${title}” (${value}) is now ${newState}`);
});

Styling with CSS

The <cbx-tree> element provides a few CSS custom properties (variables) that you can override for your needs.

| Variable | Data type | Description | | -------------------------------- | ----------- | ------------------------------------------------------- | | --cbx-tree-toggle-closed-mask | <url>¹ | Mask image for the toggle button in the collapsed state | | --cbx-tree-toggle-open-mask | <url> | Mask image for the toggle button in the expanded state | | --cbx-tree-toggle-pending-mask | <url> | Mask image for the toggle button in the pending state | | --cbx-tree-label-focus-bg | <color>² | Background color for the highlighted item’s label | | --cbx-tree-label-focus-fg | <color> | Text color for the highlighted item’s label | | --cbx-tree-nesting-indent | <length>³ | Indentation size for nested subtrees |

¹ https://developer.mozilla.org/en-US/docs/Web/CSS/url_value
² https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
³ https://developer.mozilla.org/en-US/docs/Web/CSS/length

In the following example, item toggle button’s mask is changed from the default arrow to “+/−” icons:

cbx-tree {
  --cbx-tree-toggle-closed-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14"><path d="M3 7L11 7M7 3L7 11" stroke="black"/></svg>');
  --cbx-tree-toggle-open-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14"><path d="M3 7L11 7" stroke="black"/></svg>');
}

Additionally, parts of the <cbx-tree> element can be directly styled through the ::part() pseudo-element.

[!CAUTION] Directly styling the inner parts of the tree is (to some extent) an advanced technique that comes with the risk of breaking the tree’s UI. Use it as a last resort if the desired result cannot be achieved with regular CSS inheritance.

The available ::part() pseudo-elements are listed in the following table and are shown in the picture below.

| Pseudo-element | Matched parts | | ------------------ | ---------------------------------------------------- | | ::part(tree) | The root tree and any nested subtree | | ::part(item) | Any individual item of a tree/subtree | | ::part(toggle) | Item toggle buttons | | ::part(label) | Wrappers around any item’s checkbox, icon, and title | | ::part(checkbox) | Any item’s checkbox | | ::part(icon) | Any item’s icon | | ::part(title) | Any item’s title |

Tree parts that can be styled using the ::part() selector

cbx-tree::part(title) {
  transition: scale 0.2s ease-in-out;
  transform-origin: 0 50%;
}
cbx-tree::part(title):hover {
  scale: 1.1;
}