Tree
Displays hierarchical data with support for expand / collapse, selection, cascading checks, multi-select by click, search filtering, lazy loading, keyboard operation, and node drag-and-drop.
⚠️ Breaking change (v2.5):
expanded/checkedchanged from comma-separated strings to JSON string arrays. The old formexpanded="a,b"/checked="a-1,b"no longer works — migrate toexpanded='["a","b"]'/checked='["a-1","b"]'. Why: keys containing commas broke comma-string parsing; JSON arrays unify the shape withoas-tree-select'sexpanded/value. If your keys may contain commas, rebuild the attribute using the array form when upgrading.
Basic Usage
Hierarchical tree
Click the expand arrow to show / hide child nodes; click a node label to select it. The tree is fully keyboard operable (↑/↓ move focus, → expand, ← collapse, Home/End, Space toggles a checkbox, Enter activates).
Touch adaptation: the expand arrow stays visually 20px, while its hit area grows to the 44px touch target under coarse pointers (via an invisible ::before overlay), keeping both tap accuracy and visual density.
Controlled Expansion and Selection
Initial expansion and selection
expanded is a JSON string array (e.g. '["a","b"]'), selected is the selected node key. It composes with default-expand-all (expand everything on first render; an explicit expanded wins), auto-expand-parent (automatically fill in ancestors when a deep key is injected externally), and expand-trigger="node" (clicking a node also expands / collapses it).
Checkboxes (Cascading)
Cascading checks (parent selects all, half state shown)
Checked set (checked attribute, JSON array): —
With checkable the model is cascading (same semantics as oas-tree-select): checking a parent checks all its checkable children; when every child of a parent becomes checked the parent converges to checked; partial children show the parent checkbox in half state (indeterminate).
check-strategy:all(default, parents + children all in the value) /parent(collapse fully-checked children into their parent) /child(leaves only);check-strictly: decouple parent/child — no cascading, no half state;- Data can mark
disableCheckbox: trueto disable a single node's checkbox (skipped by the cascade); use the component-leveldisabledattribute to disable all interaction.
Non-cascading checks (check-strictly) & default expand
check-strictly disables cascading; default-expand-all expands everything initially; auto-expand-parent auto-fills ancestor expansion when the host writes the expanded attribute (checking / clicking does not trigger it).
Empty state (empty text & empty slot)
The empty attribute customizes the empty text; rich content can go in template[slot="empty"].
Multi-Select by Click
Multi-select (multiple, click again to deselect)
With multiple, selected becomes a JSON string array and clicking a label appends / removes its key (click again to deselect). Checkboxes (checkable) and click-selection (multiple / selected) are two independent models and can be combined.
Expansion Behaviour
Accordion + tree lines + expand on node click
accordion: expanding a node collapses its siblings at the same level (mutually exclusive);tree-lines: renders ancestor indent guides (attribute name matchesoas-tree-select);expand-trigger="node": clicking the node text also expands / collapses (default"toggle"only reacts to the arrow);auto-expand-parent: when the host setsexpandedto a deep key (setAttribute('expanded', '["deep-key"]')), the component automatically fills in the ancestors.
Search Filtering
In-tree filtering (keeps matched branches; optional highlight)
filter is the search term (attribute-driven; the host places the search box itself, here wired to an oas-input). Matches keep all their ancestors (path context) and the whole matched subtree, independent of the expanded state; no match shows the empty state. filter-highlight wraps matched slices of the default label text in <mark>. Use the filterNode(label, node) property for custom matching (default: case-insensitive label containment).
Field Mapping
Field aliases (field-names)
Use field-names (a JSON object mapping the keys key/label/children/disabled/isLeaf/loaded to your data field names) when your data shape differs — normalization happens once at the data entry and shares the same field machinery as oas-tree-select.
Disabled States
Node-level disabled / selectable / disableCheckbox
- Node
disabled: true: the whole row is inert (no click / expand / check); - Node
selectable: false: excluded from click and keyboard selection, while checkboxes and expansion still work; - Node
disableCheckbox: true: the checkbox is disabled and skipped by the cascade; - Component-level
disabled: disables all interaction on the whole tree (read-only tree).
Imperative Methods and Scrolling
Expand all / collapse all / locate a node
Imperative methods (data stays host-controlled; the methods only recompute the expansion set / scroll position):
expandAll()/collapseAll(): expand / collapse every node;expand(keys: string[])/collapse(keys: string[]): expand / collapse the given keys;scrollTo(key, { expand?: boolean }): scroll the node into view (expands the ancestor chain by default; passexpand: falseto leave the expanded state untouched).
Lazy Loading
Lazy loading (async child fill-in on expand)
Click the expand arrow before "Folder A" to trigger loading
Lazy load failure retry (oas-load-error)
Click "Fails first"'s expand arrow to see the error event and retry
With lazy, nodes without children and not marked isLeaf / loaded are considered "not loaded": clicking the expand arrow emits oas-load (detail: { key }) and calls the load callback; the host refills the child nodes and resets the data attribute. During loading, a loading placeholder is shown in place of the expand arrow (replaceable via template[slot="toggle-loading"]); after the fill-in completes, the node auto-expands and the placeholder is removed. When the load callback returns a Promise that rejects, the component clears the loading state, rolls the expansion back, and emits oas-load-error (detail: { key, error }) — the node is clickable again to retry. Nodes marked isLeaf: true do not show an expand arrow.
Node Drag and Drop
Draggable (reorder / reparent)
Drag a node to the upper / lower / middle of a target row to insert before / after / inside it
With draggable, nodes can be dragged to reorder or reparent: dropping on the top 25% of a target row inserts before it (before), the bottom 25% inserts after it (after), and the middle (when the target is expandable) moves it inside (inner; when the target is not expandable the middle falls back to before / after by the upper / lower half). An insertion line and highlight feedback are shown during the drag; on release, oas-node-drop is emitted (detail: { dragKey, dropKey, position }) and the host updates the data and resets the data attribute. Dropping on empty tree space moves the node to the root (dropKey empty string, position: 'inner').
The full drag lifecycle is also observable: oas-node-dragstart (detail: { dragKey }), oas-node-dragover / oas-node-dragleave (detail: { dragKey, dropKey, position }; dropKey is empty and position is 'inner' over blank space), and oas-node-dragend (detail: { dragKey }) — useful for custom drag hints or cross-tree interactions.
Guard the drop targets with the property callbacks (rejected during the drag — no insertion line feedback):
tree.allowDrop = ({ dragKey, dropKey, position }) => dropKey !== 'grp-1' // disallow dropping into this node
tree.allowDrag = (node) => node.key !== 'item-1' // this node cannot be draggedLarge Data Sets (Virtual Scroll)
Virtual scroll with 10k nodes
Setting height enables virtualization (with a fixed row-height): the tree reuses oas-virtual-list to render only the nodes in the visible window. The expanded state is kept in the expanded attribute and survives scrolling and re-renders; keyboard navigation and scrollTo() keep the window in sync.
Custom Node Rendering
Custom nodes (icon + rich text)
Node content can be provided as a static skeleton via template[slot="node"] (the [data-node-label] node is bound to the node label automatically), and template[slot="toggle"] replaces the default expand arrow. After each node row renders, oas-node-render is emitted (detail: { node, element, level, expanded, selected, checked }); the host can listen and rewrite element into any icon / rich text. In this example the task-count badge is written by that event. Keyboard access and ARIA (treeitem / aria-expanded / aria-level) stay intact under custom rendering.
Directory Mode (File Browser)
Directory mode (file browser style)
With directory, the tree renders in a file-browser style: nodes with children (or not yet loaded under lazy) show a folder icon, nodes with isLeaf / no children show a file icon; the folder icon switches between collapsed / expanded, and the depth-based indent and hover row highlight follow the row styles. Combine with tree-lines to show ancestor guide lines.
Node Renaming
Inline renaming (double-click or F2, Enter commits / Esc cancels)
Double-click "R&D team" or one of its children, or press F2 after selecting a node
With can-rename, node renaming is enabled: double-click a node label (or press F2 with the node focused) enters the inline editing state; Enter / blur commits, Esc cancels (restores the old value without emitting). While editing, arrow keys stay inside the input so tree roving keyboard navigation is unaffected; mark a node renamable: false in the data to disable renaming for that node individually.
Data stays host-controlled: on commit the component only emits oas-node-rename (detail: { key, label, oldLabel }) and never mutates the data model — the host listens, writes the new label back into the data, and resets the data attribute; only then does the row show the new name. If the host does nothing, the old value is kept (no desync from one-sided component mutation).
Expand / Collapse Transition
Expand/collapse motion (toggle to compare)
With motion, expand/collapse animates with a height transition (off by default): expanding grows the newly added child rows smoothly from zero height to their natural height; collapsing first shrinks the child rows away before removing them. Duration and easing follow the --oas-transition-* tokens. In virtual-scroll mode (a height is set) the expand entrance degrades to a fade and collapse switches instantly — virtual rows use a fixed height where height animation would cause jank and misalignment. The animation respects prefers-reduced-motion (auto-disabled when the user asks for reduced motion).
Expand / Collapse Event
oas-expand (node expand / collapse)
Last action: Click an expand arrow
Every expand/collapse interaction (toggle arrow, expand-trigger="node", keyboard →/←) emits oas-expand with detail: { key, expanded, node } — node is a snapshot of the data node, so hosts can drive other views from it.
Events
Selection and check events
Selected: — · Checked: —
Five Size Tiers (size)
size scales the whole tree's font size and row padding across five tiers: xs / small / medium / large / xl (the sm / md / lg aliases are accepted as equivalents; invalid values fall back to medium). medium is the default tier (identical to not setting the attribute).
Size tiers compared
Full-row block selection area (block-node)
block-node renders rows as taller block-level hit areas: the selection/hover band already covers the whole row, and enabling this adds one tier of row height — useful for dense pointing (touch screens, frequent selection). Off by default (compact rows).
block-node toggle
Whole-tree selection switch (selectable)
selectable toggles row selection for the whole tree (default on, current behavior); setting "false" stops click/keyboard selection and no oas-select is dispatched — expanding and checking are unaffected. The node-level selectable: false data field still excludes individual nodes.
selectable switch
Select all (check-all)
check-all (effective together with checkable) renders a select-all row above the tree: checking it selects every checkable node, a partial selection shows an indeterminate checkbox, and clicking again clears the selection. disabled / disableCheckbox nodes are excluded. The default label is "Select all"; override it with check-all-label. Each toggle emits oas-check-all (detail: { checked, values }).
check-all select all
Toggle the "Select all" row or check items one by one to see the tri-state; click again to clear.
API
oas-tree
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
accordion | Accordion (only one sibling branch open at a time) | boolean | — |
auto-expand-parent | Auto-expand parents when a child is checked | boolean | — |
block-node | Full-row block selection/hover zone (rows one tier taller) | boolean | — |
can-rename | Master switch for node renaming: when on, double-click a node label or press F2 to enter inline editing; Enter commits / Esc cancels / blur commits; committing emits oas-node-rename (mark a node renamable: false in the data to exclude it individually) | boolean | — |
check-all | Renders a select-all row above the tree with a tri-state checkbox (requires checkable) | boolean | — |
check-all-label | Select-all row label (defaults to the i18n select-all copy) | string | — |
check-strategy | Check export strategy: all (default) / parent / child (with checkable cascading) | string | all |
check-strictly | Decouple parent/child checking | boolean | — |
checkable | Whether to show checkboxes | boolean | — |
checked | Set of checked node keys (JSON string array, e.g. '["a"]'; the pre-v2.5 comma string is deprecated and falls back to an empty set with a warning) | string | — |
data | Node data [{ key, label, children?, disabled?, isLeaf?, loaded?, renamable? }], JSON string | TreeNode[] | string | [] |
default-expand-all | Expand all nodes by default (applies when the expanded attribute is absent) | boolean | — |
directory | Directory mode: nodes with children (or not yet loaded under lazy) show a folder icon, nodes with isLeaf / no children show a file icon; the folder icon switches on expand / collapse | boolean | — |
disabled | Whole-tree disabled (select/check/expand/drag all inert, browsing kept) | boolean | — |
draggable | Nodes can be dragged to reorder / reparent; drop shows insertion line / highlight; release emits oas-node-drop | boolean | — |
empty | Empty-state text (when no data; no-match uses the locale no-match text) | string | — |
expand-trigger | Node expansion trigger: toggle (default, arrow only) / node (clicking the node row) | string | toggle |
expanded | Set of expanded node keys (JSON string array, e.g. '["a"]'; the pre-v2.5 comma string is deprecated and falls back to an empty set with a warning) | string | — |
field-names | Field-alias JSON (e.g. {"label":"name","value":"id","children":"subs"}; data is not copy-mapped — node-render receives your original objects) | string | — |
filter | In-tree search filter (keyword input; pair with filter-highlight and el.filterNode) | string | — |
filter-highlight | Highlight matched fragments (mark tag) | boolean | — |
height | Virtual scroll viewport height (px); setting it enables virtualized rendering for large data | string | — |
lazy | Lazy loading: nodes without children and not marked isLeaf / loaded trigger loading on expand | boolean | — |
motion | Expand/collapse height transition animation (off by default): non-virtual mode transitions row-container max-height on expand/collapse, virtual-scroll mode degrades to a fade-in on expand with instant collapse; duration/easing follow --oas-transition-*, disabled under prefers-reduced-motion | boolean | — |
multiple | Click multi-select (Ctrl/⌘-click; selected set is a JSON array) | boolean | — |
row-height | Fixed row height when virtualized (px) | string | 32 |
selectable | Whole-tree selection switch; "false" stops click/keyboard selection while expand/check keep working | string | true |
selected | Key of the selected node | string | — |
size | Five-tier tree font/padding scale (xs~xl, aliases accepted, invalid falls back to medium) | string | medium |
tree-lines | Tree indentation guide lines | boolean | — |
Property (JS property; see each row for attribute reflection)
| Property | Description | Type | Default |
|---|---|---|---|
load | Lazy-load function (property channel, (node) => Promise<children>) | (payload: { key: string }) => void | Promise<unknown> | — |
Events
| Event | Description |
|---|---|
oas-check | Check state change, detail: { key, checked } |
oas-check-all | Fired when the select-all row toggles, detail: { checked, values } |
oas-expand | Dispatched on node expand/collapse, detail: { key, expanded, node } (node is a data snapshot) |
oas-load | Lazy loading triggered, detail: { key }; the host refills children and resets the data attribute |
oas-load-error | Lazy load failed, detail: { key, error } where error is the error message string (loading clears, clickable to retry) |
oas-node-dragend | Dispatched when a drag ends, detail: { dragKey } |
oas-node-dragleave | Dispatched when a drag leaves a target, detail: { dragKey, dropKey, position } |
oas-node-dragover | Dispatched while dragging over a target, detail: { dragKey, dropKey, position } (empty area: dropKey empty, position inner) |
oas-node-dragstart | Dispatched when a drag starts, detail: { dragKey } |
oas-node-drop | Node dropped, detail: { dragKey, dropKey, position }; position is before / after / inner; an empty-string dropKey means moved to the root |
oas-node-rename | Inline rename committed (Enter / blur), detail: { key, label, oldLabel }; data stays host-controlled — the component does not mutate the model, so the new label only shows once the host updates data (keeps the old value otherwise; Esc cancel does not emit) |
oas-node-render | Dispatched for each rendered node row, detail: { node, element, level, expanded, selected, checked } (element is the node label container; the host can rewrite it into icon / rich text) |
oas-select | Node selected, detail: { key, selected } |
Slots
| Name | Description |
|---|---|
template[slot="empty"] | Custom empty-state content |
template[slot="node"] | Static row template cloned into each node label container; the [data-node-label] node is bound to the node label automatically |
template[slot="toggle"] | Static expand button template cloned into each expandable node's toggle button (replaces the default › icon) |
template[slot="toggle-loading"] | Lazy-expanding indicator (replaces the spinner) |
Node field notes:
isLeaf: truemarks an explicit leaf (no expand arrow under lazy loading);loaded: truemarks a node as fully loaded (used withchildrento avoid triggering loading repeatedly);renamable: falsedisables renaming for that node (used withcan-rename; nodes are renameable by default).