Tree

A nested list to browse and select from, inline or as a dropdown picker.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrTree } from 'ngwr/tree';

@Component({ imports: [WrTree] })
export class MyComponent {}

Single selection

Bind [(selected)] and [(expanded)] to arrays of node ids.

<wr-tree
  ariaLabel="Project files"
  [nodes]="folders"
  [(selected)]="picked"
  [(expanded)]="open"
  selectionMode="single"
/>
  • src
  • app
  • app.ts
  • main.ts
  • styles.scss
  • package.json
  • tsconfig.json
Selected: (none)

Multi selection

A plain click replaces the selection; Cmd or Ctrl + click adds and removes a row.

<wr-tree
  ariaLabel="Project files"
  [nodes]="folders"
  [(selected)]="picked"
  selectionMode="multi"
/>
  • package.json
  • tsconfig.json
Selected: (none)

Combobox mode — single

openOn="overlay" makes the tree a dropdown picker. Bind [(value)] or [formField].

<wr-tree
  openOn="overlay"
  [nodes]="folders"
  selectionMode="single"
  placeholder="Pick a folder"
  [(value)]="picked"
/>
Picked: (none)

Combobox mode — multi + chips

maxTagCount caps the chips shown before +N more. defaultExpandAll opens every parent on first open.

<wr-tree
  openOn="overlay"
  [nodes]="folders"
  selectionMode="multi"
  [maxTagCount]="2"
  [defaultExpandAll]="true"
  placeholder="Pick folders"
  [(value)]="picked"
/>
Picked: (none)

Combobox mode — width

A trigger left to size itself is 12rem wide. Change that with --wr-tree-trigger-min-width; a min-width on .wr-tree__trigger has no effect.

Virtual scroll

For a large tree, set virtualScroll and viewportHeight. rowHeight is measured when left at 0.

<wr-tree
  ariaLabel="Generated folders"
  [nodes]="bigTree"
  [(expanded)]="expanded"
  [(selected)]="picked"
  virtualScroll
  [viewportHeight]="320"
/>
  • Group 1
  • Item 1.1
  • Item 1.2
  • Item 1.3
  • Item 1.4
  • Item 1.5
  • Item 1.6
  • Item 1.7
  • Item 1.8
  • Item 1.9
  • Item 1.10
  • Item 1.11
  • Item 1.12
  • Item 1.13
  • Item 1.14
  • Item 1.15
  • Item 1.16
Picked: (none)

API

NameDescriptionTypeDefault
nodesTree data.readonly WrTreeNode<TId>[][]
selectedSelected node ids (two-way bindable).readonly TId[][]
expandedExpanded node ids (two-way bindable).readonly TId[][]
selectionModeSelection behavior.WrTreeSelectionMode'single'
disabledDisable the whole tree. Bound automatically from the field's disabled state when used with [formField].booleanfalse
readonlyRefuse 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.booleanfalse
openOnRender shape.'inline' | 'overlay''inline'
placeholderPlaceholder shown on the trigger when no node is selected.string''
ariaLabelAccessible 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. An INLINE tree puts it on the role="tree" itself, which ARIA requires to have a name — an aria-label on the <wr-tree> host never reaches it. Inside a <wr-form-field> the field's label names the tree instead.string | nullnull
clearableShow a clear-all (×) button on the trigger when at least one node is selected.booleantrue
maxTagCountCap on the number of chips rendered on the trigger before collapsing the rest into +N more (multi mode only). 0 = render every chip.number0
defaultExpandAllAuto-expand every node that has children on first open of the overlay.booleanfalse
virtualScrollWindow 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.booleanfalse
rowHeightUniform 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.number0
viewportHeightHeight 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 | string288
overscanExtra rows kept rendered above and below the viewport as scroll headroom.number6
panelClassExtra CSS classes for the overlay pane, when openOn puts the tree in one. The pane is appended to the overlay container, not to this component, so nothing in the consumer's own template encloses it and no descendant rule written around the trigger can reach it. This input is the only per-instance handle on it. A space-separated string works as well as an array. Inline trees never open an overlay, so this input does nothing for the default openOn="inline" — the host element is right there to take a class of its own.WrClassInputnull
valueForm 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.unknownundefined
(touch)Emitted on blur so a bound field can mark itself touched.void—

Types

NameDescriptionTypeDefault
WrTreeNode{ id, label, children?, disabled? }. id is what [(selected)] / [(expanded)] carry; a node without children is a leaf.interface—

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/tree's own element.

VariableDefaultDeclared on
--wr-tree-chip-font-sizevar(--wr-text-sm).wr-tree
--wr-tree-chip-gapvar(--wr-space-xs).wr-tree
--wr-tree-chip-line-heightvar(--wr-leading-snug).wr-tree
--wr-tree-chip-padding-blockvar(--wr-control-padding-y-sm).wr-tree
--wr-tree-chip-padding-inline-endvar(--wr-space-xs).wr-tree
--wr-tree-chip-padding-inline-startvar(--wr-space-sm).wr-tree
--wr-tree-chips-gapvar(--wr-space-xs).wr-tree
--wr-tree-clear-radiusvar(--wr-border-radius-pill).wr-tree
--wr-tree-font-familyvar(--wr-font-family-base).wr-tree
--wr-tree-font-sizevar(--wr-text-sm).wr-tree
--wr-tree-indent1.25rem.wr-tree
--wr-tree-list-padding-yvar(--wr-space-xs).wr-tree
--wr-tree-overlay-padding-topvar(--wr-space-xs).wr-tree-overlay
--wr-tree-panel-bordervar(--wr-border-base).wr-tree-panel
--wr-tree-panel-max-height18rem only under .wr-tree--combobox — unset elsewhere.wr-tree--combobox
--wr-tree-panel-paddingvar(--wr-space-sm).wr-tree-panel
--wr-tree-panel-radiusvar(--wr-border-radius-base).wr-tree-panel
--wr-tree-row-gapvar(--wr-space-sm).wr-tree
--wr-tree-row-padding-xvar(--wr-space-sm).wr-tree
--wr-tree-row-padding-yvar(--wr-space-xs).wr-tree +1 variant override
--wr-tree-row-radiusvar(--wr-border-radius-sm).wr-tree
--wr-tree-row-selected-font-weightvar(--wr-font-weight-medium).wr-tree
--wr-tree-toggle-radiusvar(--wr-border-radius-sm).wr-tree
--wr-tree-trigger-bgvar(--wr-color-surface).wr-tree +1 variant override
--wr-tree-trigger-bordervar(--wr-border-base).wr-tree
--wr-tree-trigger-colorvar(--wr-color-on-surface).wr-tree +1 variant override
--wr-tree-trigger-font-familyvar(--wr-font-family-base).wr-tree
--wr-tree-trigger-font-sizevar(--wr-control-font-size-md).wr-tree
--wr-tree-trigger-gapvar(--wr-space-sm).wr-tree
--wr-tree-trigger-line-heightvar(--wr-control-line-height-md).wr-tree
--wr-tree-trigger-min-width12rem only under .wr-tree--combobox — unset elsewhere.wr-tree--combobox
--wr-tree-trigger-multi-padding-xvar(--wr-space-sm).wr-tree
--wr-tree-trigger-multi-padding-yvar(--wr-space-xs).wr-tree
--wr-tree-trigger-padding-xvar(--wr-control-padding-x-md).wr-tree
--wr-tree-trigger-padding-yvar(--wr-control-padding-y-md).wr-tree
--wr-tree-trigger-radiusvar(--wr-control-radius-md).wr-tree

Dependencies

Angular CDKoverlay, portal
Built onForm, Overlay

See also