Beta FeatureThis feature is currently in beta, and needs to be imported from
@servicetitan/anvil2/beta.While we hope to minimize breaking changes, they may occur due to feedback we receive or other improvements. These will always be documented in the changelog and communicated in Slack.Please reach out in the #ask-designsystem channel with any questions or feedback!- Implementation
- TreeSelectField Props
- TreeSelectFieldSync Props
- TreeSelectFieldNode Type
- TreeSelectFieldValue Type
Overview
The tree select field family includes two components for different use cases:TreeSelectField— For async data loading with support for lazy branch expansion- Includes automatic debouncing of the search input (configurable via the
debounceMsprop) - Includes automatic LRU caching of search results and loaded children (configurable via the
cacheprop)
- Includes automatic debouncing of the search input (configurable via the
TreeSelectFieldSync— For client-side filtering of static tree option arrays
TreeSelectFieldSync (Static Options)
UseTreeSelectFieldSync when you have a static tree of options that can be filtered client-side.Filtering and Sorting
By default,TreeSelectFieldSync uses match-sorter to filter options by their label and searchText fields. The tree structure is preserved: parent nodes of any matching node remain visible so the hierarchy is clear.You can customize this behavior in two ways:Using match-sorter options
Pass a match-sorter options object to customize the default filtering and sorting behavior:Using a custom filter function
Pass a function for full control over both filtering and tree structure. The returned array determines the exact tree shown in the dropdown:TreeSelectField (Async Loading)
UseTreeSelectField when tree data needs to be fetched from an API or when branches load their children on demand.Basic Async Loading
Lazy Branch Loading
Branches withchildren: null are treated as unloaded. When expanded, loadOptions is called with the parent node to fetch its children:loadOptions should use children: null for branches whose children haven’t been loaded yet:Progressive (Multi-Level) Loading
Lazy loading is recursive: when the children returned for one branch are themselves branches withchildren: null, each level loads on demand as the user drills in.
This is the common shape for a file browser or a deep location hierarchy where you
never want to fetch the whole tree up front.invalidate() on the ref to drop the cache and re-fetch from the top:Restoring a Selection Under Unloaded Branches
With lazy loading, a selected node can sit under branches that haven’t been fetched yet — most commonly when you restore a persisted selection on mount, before the user has expanded anything. In linked mode the field shows a parent as checked or indeterminate by looking at its children, but those children aren’t loaded, so the ancestor checkboxes can’t reflect the selection until the branch is expanded.Provide the selected value’s ancestry via the optionalpath field
(ancestor ids, root → parent) so the field can mark the correct ancestor
branches immediately, with no extra fetching:path by hand for selections made through the UI:
when the field emits a selection whose ancestors are loaded, it stamps
path onto the value for you. Persisting value as-is and restoring it
later round-trips the ancestry automatically.Without path, the field still resolves ancestry for any branch the user has
expanded this session (it remembers child→parent relationships as branches
load). A selection whose ancestors have never been loaded simply shows its
branches unchecked until they’re expanded — at which point the state
self-corrects. Supplying path is what makes a cold restore correct up front.During search, set
childCount on branch nodes so the field can tell a
partially-matched branch from a fully-selected one — see
Search and childCount.Selection Modes
TheselectionMode prop controls how nodes relate to each other during selection:"linked"(default) — Parent-child cascading. Selecting a parent checks all children; selecting all children checks the parent. ThevalueConsistsOfprop controls which nodes appear in the value array."independent"— Each node is selected independently with no cascading."single"— Only one node can be selected at a time. The menu closes after selection.
Value Strategies
In linked selection mode, thevalueConsistsOf prop controls which checked nodes appear in the value array:| Strategy | Selectable Nodes | Value Contains |
|---|---|---|
"ALL" | Any node | All checked nodes |
"BRANCH_PRIORITY" | Any node | Branches replace their leaves when fully checked |
"BRANCH_ONLY" | Branches only | Only branches (leaves are visible but inert) |
"LEAF_PRIORITY" (default) | Leaves + expand branches | Leaves and childless branches |
"LEAF_ONLY" | Leaves only | Strictly leaf nodes |
valueConsistsOf restricts which node types can be selected: "LEAF_ONLY" allows only leaves, "BRANCH_ONLY" allows only branches, and the other strategies allow any node.What ends up in value
The same user action produces a different value array depending on the
strategy. Given this tree, with the user checking Building B (which checks
its only floor and both of that floor’s rooms):valueConsistsOf | Resulting value (by label) |
|---|---|
"ALL" | Building B, 3rd Floor, Conference Room, Focus Room |
"BRANCH_PRIORITY" | Building B (the branch replaces its fully-checked subtree) |
"BRANCH_ONLY" | Building B, 3rd Floor (only branches; rooms are inert) |
"LEAF_PRIORITY" | Conference Room, Focus Room (leaves only) |
"LEAF_ONLY" | Conference Room, Focus Room (leaves only) |
BRANCH_PRIORITY is the right choice when “select the whole branch” should
read as a single chip; LEAF_ONLY is right when the consumer only ever wants
concrete items (e.g. people, not teams) in the submitted value.Display Modes
Control how the options menu is displayed using thedisplayMenuAs prop:Caching
TreeSelectField caches loadOptions results by default using two separate LRU caches: one for search results and one for lazy-loaded children. Configure caching behavior:Clearing the Cache
Use a ref to imperatively clear the cache:Invalidating Options
Callinvalidate() to clear the cache and reload options from the data source:TreeSelectFieldSync handles this automatically when its options prop reference changes.Initial Load Behavior
Control when options are first loaded with theinitialLoad prop:Controlled Expansion
By default, expansion state is managed internally. UsedefaultExpandLevel to set the initial depth:expandedIds and onExpandedIdsChange. Because the set
lives in your state, external UI can drive expansion directly — for example, a
button that drills the tree open to a specific path:expandAll() and collapseAll() for when
you don’t need to track the exact set:Controlled Search
By default the search input is uncontrolled. ProvidesearchValue and
onSearchChange to keep it in sync with your own state — useful when an
external search box should drive the field, or when you filter loadOptions
server-side and want to react to the query yourself.onSearchChange, and updating searchValue
externally re-filters the tree — the two stay in lockstep.Search and childCount
When a search filter hides some of a branch’s children, the field only sees
the matches that remain. To avoid showing a branch as fully checked when an
unselected child is merely filtered out of view, set childCount (the
branch’s true number of children) on branch nodes. The field compares the
loaded children against childCount: if fewer are present than the real
total, a branch with some — but not provably all — children selected renders
as indeterminate rather than checked.TreeSelectFieldSync derives childCount automatically from its static
options, so client-side filtering is always accurate. For async
TreeSelectField, return childCount from loadOptions (it should reflect
the unfiltered count). Without it, a partially-filtered branch falls back to
reflecting only its visible children.Field States
Error State
Hint and Description
Required Field
Disabled and ReadOnly
Whendisabled is set, users cannot interact with the field.readOnly is set, users can browse the tree but cannot change the selection.Prefix and Suffix
Markdown in labels
Thelabel prop supports inline markdown: bold (**text**), italic (*text*), highlight (==text==), and code (`text`).Hide the label
UsehideLabel to visually hide the label. The label string remains accessible to screen readers.Disabled Nodes
Individual nodes can be disabled by settingdisabled: true:Virtualization
Passvirtualize to enable windowed rendering for large trees. Only visible rows are mounted to the DOM.Disabling Search
PassdisableSearch to hide the search input:Chip Display
In multi-select mode, selected values appear as removable chips. Control chip layout withsingleRow and maxChips:Chip Customization
Customize the appearance of individual chips usinggetChipProps. The callback receives each selected value and returns partial ChipProps (e.g., color, icon, avatar). Core chip props (label, onClose, className, title, size) are managed by the component and excluded from the callback’s return type.