Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelogApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UIARC Radiant Components
v4.9DocsComponentsTokensSynthesizer
Getting StartedFrameworksServer RenderingDesign TokensThemingTheme SynthesizerTypographyUtilitiesAll ComponentsAccessibilityBrowser SupportChangelogContributingStatsApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Tree Select

Dropdown select whose panel is a hierarchical tree: categories, instrument banks, folder pickers. Group nodes expand and collapse; only leaf nodes are selectable.

ComponentsTree Select
inputinteractive
<arc-tree-select>

Overview

Tree Select combines the trigger anatomy of Select with a hierarchical tree panel. Instead of a flat list of options, the dropdown presents expandable groups whose leaves are the actual choices: an instrument bank organized by family, a category taxonomy, a folder structure. The trigger shows the chosen leaf together with a muted breadcrumb of its ancestor path, so "Violin" reads as "Strings / Violin" and never loses its context. Selection is leaf-only. Nodes with children act as group headers: they expand and collapse but can never be chosen. That keeps single-select semantics clean, since the submitted value is always one unambiguous leaf, never a branch that might mean "everything under it". Branches containing the current value expand automatically when the panel opens, so the selection is always visible without hunting. Tree Select implements the ARIA combobox pattern with a tree popup. Keyboard users open the panel with Enter, Space, or an arrow key, walk rows with Arrow Up and Down, expand and collapse groups with Arrow Right and Left, confirm a leaf with Enter, and dismiss with Escape. Typing jumps to the row starting with those letters, exactly as in Select. The component participates in native forms through ElementInternals, submitting the selected leaf value under its `name`.

Guidelines

When to use

  • Use Tree Select when the options have a real hierarchy the user thinks in: instrument families, product categories, folder trees
  • Give every node a stable value, including group headers. Group values drive expanded-values and appear in the arc-change path detail
  • Keep the tree shallow; two or three levels is comfortable inside a dropdown panel
  • Pre-expand the branches users need most via expanded-values instead of making them dig
  • Always provide a visible label so users understand what they are choosing
  • Use disabled nodes for temporarily unavailable choices rather than removing them, so the structure stays recognizable

When not to use

  • Do not use Tree Select for a flat list. Use Select, which is simpler for both hands and screen readers
  • Do not use it when users need to type to filter a large set. Use Combobox, whose text field owns the keystrokes
  • Do not use it for browsing or navigation outside a form. Use Tree View, which is a standalone tree without a trigger or form value
  • Do not expect group headers to be selectable. If a branch itself must be a valid choice, add an explicit leaf such as "All Strings" inside it
  • Do not nest deeper than three levels. A dropdown panel is the wrong home for a deep tree; consider a dedicated picker dialog instead

Features

  • Hierarchical tree panel with expandable, collapsible group headers
  • Leaf-only selection keeps single-select semantics unambiguous
  • Trigger breadcrumb shows the ancestor path muted beside the leaf label
  • Branches containing the selected value auto-expand when the panel opens
  • Initially expanded branches via the expanded-values property
  • Full keyboard support: arrows navigate and expand, Enter selects, Escape closes
  • Type-ahead jumps to rows by their first letters, as in a native select
  • Disabled nodes render but are skipped by keyboard and cannot be selected
  • Native form participation via ElementInternals, including required validation
  • Neutral depth rails mark nesting structure without carrying state

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available. Use the Web Component directly or a framework wrapper.

<script type="module" src="@arclux/arc-ui"></script>

<arc-tree-select
  label="Instrument"
  name="instrument"
  placeholder="Pick an instrument..."
></arc-tree-select>

<script>
  const treeSelect = document.querySelector('arc-tree-select');

  // items is a property, not an attribute: assign the tree from script.
  treeSelect.items = [
    { value: 'keys', label: 'Keys', children: [
      { value: 'grand-piano', label: 'Grand Piano' },
      { value: 'rhodes', label: 'Rhodes' },
    ] },
    { value: 'strings', label: 'Strings', children: [
      { value: 'violin', label: 'Violin' },
      { value: 'cello', label: 'Cello' },
    ] },
    { value: 'percussion', label: 'Percussion', children: [
      { value: 'timpani', label: 'Timpani' },
    ] },
  ];

  // Pre-expand a branch, or let auto-expand follow the selected value.
  treeSelect.expandedValues = ['strings'];

  treeSelect.addEventListener('arc-change', (e) => {
    // e.detail.path holds the ancestor group values, root first.
    console.log('Selected:', e.detail.value, 'in', e.detail.path.join(' / '));
  });
</script>

API

valuestring''
The selected leaf's value. Setting it programmatically updates the trigger's breadcrumb label, and the branches containing it auto-expand the next time the panel opens.
placeholderstring'Select...'
Hint text displayed inside the trigger when no leaf is selected. It disappears once a value is chosen.
labelstring''
Visible label rendered above the trigger. Also serves as the accessible name. Always provide one for accessibility compliance.
namestring''
Form field name submitted with the selected leaf value via ElementInternals.
disabledbooleanfalse
When true, the trigger becomes non-interactive: it cannot be opened, focused, or clicked, and renders with reduced opacity.
errorstring''
Error message displayed below the trigger. When set, the trigger border turns red.
itemsArray<{value: string, label: string, children?: Array<object>, disabled?: boolean}>[]
Recursive tree of nodes. A node with a non-empty children array is a group header: it expands and collapses but can never be selected. A node without children is a selectable leaf. disabled nodes render but cannot be reached by keyboard or selected, and a disabled group hides its children.
expandedValuesstring[][]
Values of group nodes to render initially expanded. Attribute: expanded-values (JSON array). Branches containing the selected value auto-expand on open regardless of this list.
size'sm' | 'md' | 'lg''md'
Controls the trigger size.
openbooleanfalse
Controls whether the tree panel is visible. Automatically set to false when a leaf is selected, Escape is pressed, or the user clicks outside. Held at false while disabled.
formAssociatedbooleantrue
propertiesobject{ // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped: // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers, so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. required participates in constraint validation below; readonly reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidatesbooleantrue
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage
requiredbooleanfalse
readonlybooleanfalse

Methods

checkValidity()boolean
Whether the control currently satisfies its constraints, per the native constraint-validation API. Fires invalid on the element when it does not, and reports nothing to the user.
reportValidity()boolean
As checkValidity(), but also shows the browser's validation message against the control when it fails.

Events

arc-change
Fired when a leaf is selected. detail.value is the leaf value, detail.label its label, and detail.path the array of ancestor group values from root to parent.

See Also