Installation
import { WrTree, type WrTreeNode } from 'ngwr/tree';
@Component({ imports: [WrTree] })
export class MyComponent {
protected readonly nodes: readonly WrTreeNode[] = [
{ id: 'src', label: 'src', children: [{ id: 'src/app.ts', label: 'app.ts' }] },
];
}Single selection
Click a row to select. Arrow keys navigate, ←/→ collapse/expand, Enter or Space selects, Home/End jump to ends.
<wr-tree
[nodes]="folders"
[(selected)]="picked"
[(expanded)]="open"
selectionMode="single"
/>Multi selection
Cmd / Ctrl + click toggles individual selections in multi mode.
<wr-tree
[nodes]="folders"
[(selected)]="picked"
selectionMode="multi"
/>
<!-- Cmd / Ctrl + click toggles individual selections. -->Combobox mode — single
openOn="overlay" turns the tree into a <wr-select>-style trigger that opens a popover. Bind the value model with [(value)] or [formField]; classic [(ngModel)] / [formControl] keep working. Replaces the standalone wr-tree-select.
<!-- Combobox shape — opens an overlay containing the tree.
A signal-forms native control (FormValueControl). Replaces wr-tree-select. -->
<wr-tree
openOn="overlay"
[nodes]="folders"
selectionMode="single"
placeholder="Pick a folder"
[(value)]="picked"
/>Combobox mode — multi + chips
maxTagCount caps visible chips before collapsing into +N more. defaultExpandAll auto-expands every parent on first open.
<wr-tree
openOn="overlay"
[nodes]="folders"
selectionMode="multi"
[maxTagCount]="2"
[defaultExpandAll]="true"
placeholder="Pick folders"
[(value)]="picked"
/>Virtual scroll
Set virtualScroll to window a large tree — only ~one viewport of rows stays in the DOM. rowHeight (0 = auto-measure the first row) and viewportHeight size the window; keyboard nav switches to aria-activedescendant so Arrow / Home / End / Enter keep working across un-rendered rows. Works inline and in openOn=overlay. The tree below holds 5,020 nodes.
<!-- 5,000+ nodes; only ~one viewport of rows stays in the DOM. -->
<wr-tree
[nodes]="bigTree"
[(expanded)]="expanded"
[(selected)]="picked"
virtualScroll
[viewportHeight]="320"
/>API
| Name | Description | Type | Default |
|---|---|---|---|
nodes | Tree data. | readonly WrTreeNode<TId>[] | [] |
selected | Selected node ids (two-way bindable). | readonly TId[] | [] |
expanded | Expanded node ids (two-way bindable). | readonly TId[] | [] |
selectionMode | Selection behavior. | WrTreeSelectionMode | 'single' |
disabled | Disable the whole tree. Bound automatically from the field's disabled state when used with [formField]. | boolean | false |
readonly | Refuse selection changes while the tree stays focusable and the value still submits. Bound automatically from the field's readonly state when used with [formField]. Expanding and collapsing keep working: a branch is NAVIGATION, not a value, so a read-only tree is still browsable — it is picking and un-picking that stops. In overlay mode the trigger additionally refuses to open, because there the panel exists only to choose from and it already shows what is chosen. aria-readonly rides on the combobox trigger only; role tree does not support the state, so the inline shape has nothing valid to mirror it onto. | boolean | false |
openOn | Render shape. | 'inline' | 'overlay' | 'inline' |
placeholder | Placeholder shown on the trigger when no node is selected. | string | '' |
ariaLabel | Accessible name of the overlay trigger. Falls back to the placeholder, then to select.label — a role="combobox" with nothing selected and no placeholder otherwise has no name at all. | string | null | null |
clearable | Show a clear-all (×) button on the trigger when at least one node is selected. | boolean | true |
maxTagCount | Cap on the number of chips rendered on the trigger before collapsing the rest into +N more (multi mode only). 0 = render every chip. | number | 0 |
defaultExpandAll | Auto-expand every node that has children on first open of the overlay. | boolean | false |
virtualScroll | Window the visible-node list so a large tree keeps only ~one viewport of rows in the DOM. Opt-in and OFF by default — a tree without it renders byte-identically to today. Works in both inline and overlay shapes; while on, keyboard navigation switches to the aria-activedescendant pattern so Arrow / Home / End / Enter keep working across un-rendered rows. | boolean | false |
rowHeight | Uniform row height in px used to map scroll offset to node index. 0 (default) measures the first rendered row once and reuses it, so it adapts to the active density / touch target automatically. Read only when virtualScroll is on. | number | 0 |
viewportHeight | Height of the scroll viewport when virtualScroll is on — a number (px) or any CSS length ('60vh'). A numeric px value lets the server prerender the exact first window. | number | string | 288 |
overscan | Extra rows kept rendered above and below the viewport as scroll headroom. | number | 6 |
value | Form value — the current selection as seen by a bound field. Bound by [formField], or two-way via [(value)]. Shape follows selectionMode: TId | null in single mode, readonly TId[] in multi mode. Works in both openOn modes; [(selected)] stays the inline-native API and always carries an array, whatever the selection mode. | unknown | undefined |
(touch) | Emitted on blur so a bound field can mark itself touched. | void | — |
Types
| Name | Description | Type | Default |
|---|---|---|---|
WrTreeNode | { id, label, children?, disabled?, icon? }. id is what [(selected)] / [(expanded)] carry; a node without children is a leaf. | interface | — |
CSS variables
Custom properties ngwr/tree publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.
| Variable | Default | Declared on |
|---|---|---|
--wr-tree-indent | 1.25rem | .wr-tree |
--wr-tree-panel-max-height | 18rem only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-bg | var(--wr-color-surface) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox +1 variant override |
--wr-tree-trigger-border | var(--wr-color-outline) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-color | var(--wr-color-on-surface) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox +1 variant override |
--wr-tree-trigger-font-size | var(--wr-control-font-size-md) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-line-height | var(--wr-control-line-height-md) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-min-width | 12rem only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-padding-x | var(--wr-control-padding-x-md) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-padding-y | var(--wr-control-padding-y-md) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |
--wr-tree-trigger-radius | var(--wr-control-radius-md) only under .wr-tree--combobox — unset elsewhere | .wr-tree--combobox |