Tree Select
Material Design 3 hierarchical dropdown selection component
Tree Select
Tree Select is a hierarchical dropdown component that allows users to select from nested options. Itβs ideal for categories, organizational structures, file systems, or any parent-child data relationships. Uses the native HTML Popover API for dropdown behavior.
Basic Usage
Basic Tree Select
html
<div id="demo-tree-select" class="tree-select">
<button class="tree-select-trigger" popovertarget="demo-tree-select-options" aria-haspopup="tree" aria-expanded="false">
<span class="tree-select-value">Select a category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="demo-tree-select-options" popover="auto" role="tree" aria-label="Categories">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf" role="treeitem" aria-selected="false" tabindex="0">
<span class="tree-select-node-label">Category 1</span>
</div>
<div class="tree-select-node tree-select-node-leaf" role="treeitem" aria-selected="false" tabindex="-1">
<span class="tree-select-node-label">Category 2</span>
</div>
<div class="tree-select-node tree-select-node-leaf" role="treeitem" aria-selected="false" tabindex="-1">
<span class="tree-select-node-label">Category 3</span>
</div>
</div>
</div>
</div>
<script>
(() => {
const root = document.getElementById('demo-tree-select');
if (!root || root.dataset.enhanced) return;
root.dataset.enhanced = 'true';
const trigger = root.querySelector('.tree-select-trigger');
const value = root.querySelector('.tree-select-value');
const dropdown = root.querySelector('.tree-select-dropdown');
const items = [...root.querySelectorAll('[role="treeitem"]')];
function selectItem(item) {
items.forEach(candidate => {
const selected = candidate === item;
candidate.setAttribute('aria-selected', String(selected));
candidate.classList.toggle('tree-select-node-selected', selected);
});
value.textContent = item.textContent.trim();
value.classList.add('tree-select-value-selected');
dropdown.hidePopover();
trigger.focus();
}
items.forEach((item, index) => {
item.addEventListener('click', () => selectItem(item));
item.addEventListener('keydown', event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
selectItem(item);
} else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault();
const offset = event.key === 'ArrowDown' ? 1 : -1;
const next = (index + offset + items.length) % items.length;
items[index].tabIndex = -1;
items[next].tabIndex = 0;
items[next].focus();
}
});
});
dropdown.addEventListener('toggle', event => {
const open = event.newState === 'open';
root.classList.toggle('tree-select-open', open);
trigger.setAttribute('aria-expanded', String(open));
if (open) {
const selected = items.find(item => item.getAttribute('aria-selected') === 'true');
(selected || items[0]).focus();
}
});
})();
</script>With Dropdown Open
Tree Select Dropdown
html
<div class="tree-select">
<button class="tree-select-trigger" popovertarget="tree-select-2">
<span class="tree-select-value">Select a category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-2" popover="auto">
<div class="tree-select-search">
<input type="text" name="tree-select-search" class="tree-select-search-input" placeholder="Search..." aria-label="Search categories" />
</div>
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-expanded">
<button class="tree-select-node-toggle">
<svg class="tree-select-node-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<span class="tree-select-node-label">Electronics</span>
</div>
<div class="tree-select-children">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Smartphones</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Laptops</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Tablets</span>
</div>
</div>
<div class="tree-select-node">
<button class="tree-select-node-toggle">
<svg class="tree-select-node-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<span class="tree-select-node-label">Clothing</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Books</span>
</div>
</div>
</div>
</div>With Selection
Tree Select with Selection
Electronics / Smartphones
html
<div class="tree-select">
<div class="tree-select-trigger" role="combobox" aria-expanded="false" tabindex="0">
<span class="tree-select-value tree-select-value-selected">
<span class="tree-select-path">Electronics / Smartphones</span>
</span>
<button type="button" class="tree-select-clear" aria-label="Clear selection">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="tree-select-dropdown" id="tree-select-3" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-expanded">
<button class="tree-select-node-toggle">
<svg class="tree-select-node-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<span class="tree-select-node-label">Electronics</span>
</div>
<div class="tree-select-children">
<div class="tree-select-node tree-select-node-leaf tree-select-node-selected">
<span class="tree-select-node-label">Smartphones</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Laptops</span>
</div>
</div>
</div>
</div>
</div>Variants
Outlined (Default)
Outlined Tree Select
html
<div class="tree-select tree-select-outlined">
<button class="tree-select-trigger" popovertarget="tree-select-outlined">
<span class="tree-select-value">Select category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-outlined" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>Filled
Filled Tree Select
html
<div class="tree-select tree-select-filled">
<button class="tree-select-trigger" popovertarget="tree-select-filled">
<span class="tree-select-value">Select category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-filled" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>Sizes
Small
Small Tree Select
html
<div class="tree-select tree-select-sm">
<button class="tree-select-trigger" popovertarget="tree-select-sm">
<span class="tree-select-value">Select</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-sm" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>Large
Large Tree Select
html
<div class="tree-select tree-select-lg">
<button class="tree-select-trigger" popovertarget="tree-select-lg">
<span class="tree-select-value">Select category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-lg" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>With Checkboxes (Multiple Selection)
Tree Select with Checkboxes
html
<div class="tree-select tree-select-multiple">
<button class="tree-select-trigger" popovertarget="tree-select-checkbox">
<span class="tree-select-value">Select categories</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-checkbox" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-expanded">
<label class="tree-select-checkbox">
<input type="checkbox" class="tree-select-checkbox-input" />
<span class="tree-select-checkbox-box"></span>
</label>
<button class="tree-select-node-toggle">
<svg class="tree-select-node-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<span class="tree-select-node-label">Electronics</span>
</div>
<div class="tree-select-children">
<div class="tree-select-node tree-select-node-leaf tree-select-node-selected">
<label class="tree-select-checkbox">
<input type="checkbox" class="tree-select-checkbox-input" checked />
<span class="tree-select-checkbox-box"></span>
</label>
<span class="tree-select-node-label">Smartphones</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<label class="tree-select-checkbox">
<input type="checkbox" class="tree-select-checkbox-input" />
<span class="tree-select-checkbox-box"></span>
</label>
<span class="tree-select-node-label">Laptops</span>
</div>
</div>
</div>
</div>
</div>States
Error State
Error State
Category
Please select a category
html
<div class="form-group">
<span class="form-label form-label-required">Category</span>
<div class="tree-select tree-select-error">
<button class="tree-select-trigger" popovertarget="tree-select-error">
<span class="tree-select-value">Select a category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-error" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>
<span class="helper-text helper-text-error">Please select a category</span>
</div>Disabled
Disabled State
html
<div class="tree-select tree-select-disabled">
<button class="tree-select-trigger" disabled>
<span class="tree-select-value">Select a category</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>Loading
Loading State
html
<div class="tree-select tree-select-loading">
<button class="tree-select-trigger" disabled>
<span class="tree-select-value">Loading categories...</span>
<span class="tree-select-spinner"></span>
</button>
</div>With Icons
Tree Select with Icons
html
<div class="tree-select">
<button class="tree-select-trigger" popovertarget="tree-select-icons">
<span class="tree-select-value">Select location</span>
<svg class="tree-select-arrow" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="tree-select-dropdown" id="tree-select-icons" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-expanded">
<button class="tree-select-node-toggle">
<svg class="tree-select-node-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
<svg class="tree-select-node-custom-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<span class="tree-select-node-label">North America</span>
</div>
<div class="tree-select-children">
<div class="tree-select-node tree-select-node-leaf">
<svg class="tree-select-node-custom-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
</svg>
<span class="tree-select-node-label">United States</span>
</div>
<div class="tree-select-node tree-select-node-leaf">
<svg class="tree-select-node-custom-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
</svg>
<span class="tree-select-node-label">Canada</span>
</div>
</div>
</div>
</div>
</div>API Reference
Class Names
| Class | Description |
|---|---|
.tree-select |
Base container (required) |
.tree-select-trigger |
Trigger button (use with popovertarget) |
.tree-select-value |
Selected value display |
.tree-select-value-selected |
Has selection state |
.tree-select-path |
Path breadcrumb display |
.tree-select-arrow |
Dropdown arrow icon |
.tree-select-clear |
Clear selection button |
.tree-select-dropdown |
Dropdown container (use with popover attribute) |
.tree-select-search |
Search input container |
.tree-select-search-input |
Search input field |
.tree-select-options |
Options container |
.tree-select-node |
Tree node |
.tree-select-node-expanded |
Expanded node |
.tree-select-node-leaf |
Leaf node (no children) |
.tree-select-node-selected |
Selected node |
.tree-select-node-toggle |
Expand/collapse toggle |
.tree-select-node-icon |
Toggle icon |
.tree-select-node-label |
Node label text |
.tree-select-node-custom-icon |
Custom node icon |
.tree-select-children |
Child nodes container |
.tree-select-checkbox |
Checkbox for multi-select |
.tree-select-outlined |
Outlined variant |
.tree-select-filled |
Filled variant |
.tree-select-sm |
Small size |
.tree-select-lg |
Large size |
.tree-select-multiple |
Multiple selection mode |
.tree-select-error |
Error state |
.tree-select-disabled |
Disabled state |
.tree-select-loading |
Loading state |
.tree-select-spinner |
Loading spinner |
HTML Structure with Popover API
<div class="tree-select">
<button class="tree-select-trigger" popovertarget="dropdown-id">
<span class="tree-select-value">Select a category</span>
<svg class="tree-select-arrow">...</svg>
</button>
<div class="tree-select-dropdown" id="dropdown-id" popover="auto">
<div class="tree-select-options">
<div class="tree-select-node tree-select-node-leaf">
<span class="tree-select-node-label">Category 1</span>
</div>
</div>
</div>
</div>
With Clear Button (Clearable)
When using a clear button, use a div with role="combobox" instead of a nested button:
<div class="tree-select">
<div class="tree-select-trigger" role="combobox" aria-expanded="false" tabindex="0">
<span class="tree-select-value tree-select-value-selected">
<span class="tree-select-path">Selected Item</span>
</span>
<button type="button" class="tree-select-clear" aria-label="Clear selection">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<svg class="tree-select-arrow">...</svg>
</div>
<div class="tree-select-dropdown" id="dropdown-id" popover="auto">...</div>
</div>
Color Variants
Color Variants
html
<div class="tree-select tree-select-primary">
<button class="tree-select-trigger" popovertarget="demo-tree-color-primary">Primary Tree Select</button>
</div>
<div class="tree-select tree-select-secondary">
<button class="tree-select-trigger" popovertarget="demo-tree-color-secondary">Secondary Tree Select</button>
</div>
<div class="tree-select tree-select-tertiary">
<button class="tree-select-trigger" popovertarget="demo-tree-color-tertiary">Tertiary Tree Select</button>
</div>
<div class="tree-select tree-select-info">
<button class="tree-select-trigger" popovertarget="demo-tree-color-info">Info Tree Select</button>
</div>
<div class="tree-select tree-select-success">
<button class="tree-select-trigger" popovertarget="demo-tree-color-success">Success Tree Select</button>
</div>
<div class="tree-select tree-select-warning">
<button class="tree-select-trigger" popovertarget="demo-tree-color-warning">Warning Tree Select</button>
</div>
<div class="tree-select tree-select-error">
<button class="tree-select-trigger" popovertarget="demo-tree-color-error">Error Tree Select</button>
</div>Ghost Variant
Ghost Tree Select
html
<div class="tree-select tree-select-ghost">
<button class="tree-select-trigger">Ghost Tree Select (no border)</button>
</div>Related Components
- Select - Single dropdown
- Multi-Select - Multiple selection
- Autocomplete - Search with suggestions