Cascade Select
Displays nested options in cascading dropdown panels.
Anatomy
<CascadeSelect.Root>
<CascadeSelect.Label />
<CascadeSelect.Control>
<CascadeSelect.Trigger>
<CascadeSelect.ValueText />
<CascadeSelect.Indicator />
</CascadeSelect.Trigger>
<CascadeSelect.ClearTrigger />
</CascadeSelect.Control>
<CascadeSelect.Positioner>
<CascadeSelect.Content>
<CascadeSelect.List>
<CascadeSelect.Item>
<CascadeSelect.ItemText />
<CascadeSelect.ItemIndicator />
</CascadeSelect.Item>
</CascadeSelect.List>
</CascadeSelect.Content>
</CascadeSelect.Positioner>
<CascadeSelect.HiddenInput />
</CascadeSelect.Root>
Examples
Controlled
Use the value and onValueChange props to control the selected value.
Root Provider
An alternative way to control the cascade select is to use the RootProvider component and the useCascadeSelect hook.
This gives you access to state and methods from outside the component.
Multiple
Enable multiple selection with the multiple prop. Users can select more than one leaf value.
Hover Trigger
Use highlightTrigger="hover" to highlight items on hover instead of requiring keyboard navigation or click.
Allow Parent Selection
By default, only leaf nodes can be selected. Use allowParentSelection to allow branch nodes to be selected as well.
Events
Use onValueChange, onHighlightChange, and onOpenChange to respond to state changes.
Guides
Building the Tree
The cascade select uses createCascadeCollection to define the hierarchical data. Provide nodeToValue and
nodeToString functions along with the rootNode to configure the collection.
const collection = createCascadeCollection({
nodeToValue: (node) => node.value,
nodeToString: (node) => node.label,
rootNode: {
label: 'Root',
value: 'root',
children: [
{
label: 'Electronics',
value: 'electronics',
children: [
{ label: 'Phones', value: 'phones' },
{ label: 'Laptops', value: 'laptops' },
],
},
],
},
})
Rendering Panels
The cascade select renders one panel per level of depth. Use a recursive component to render the nested lists. Each
panel is determined by which item is currently highlighted — use getItemState with highlightedChild and
highlightedIndex to recurse into the next level.
const TreeNode = ({ node, indexPath = [], value = [] }) => {
const api = useCascadeSelectContext()
const nodeState = api.getItemState({ item: node, indexPath, value })
return (
<>
<CascadeSelect.List item={node} indexPath={indexPath} value={value}>
{collection.getNodeChildren(node).map((child, i) => (
<CascadeSelect.Item
key={collection.getNodeValue(child)}
item={child}
indexPath={[...indexPath, i]}
value={[...value, collection.getNodeValue(child)]}
>
<CascadeSelect.ItemText>{collection.stringifyNode(child)}</CascadeSelect.ItemText>
{collection.isBranchNode(child) ? (
<ChevronRightIcon />
) : (
<CascadeSelect.ItemIndicator>✓</CascadeSelect.ItemIndicator>
)}
</CascadeSelect.Item>
))}
</CascadeSelect.List>
{nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && (
<TreeNode
node={nodeState.highlightedChild}
indexPath={[...indexPath, nodeState.highlightedIndex]}
value={[...value, collection.getNodeValue(nodeState.highlightedChild)]}
/>
)}
</>
)
}
Hidden Input
The CascadeSelect.HiddenInput component renders a native <input> element that is visually hidden but present in the
DOM, enabling native form submission with the selected value.
<CascadeSelect.Root>
<CascadeSelect.HiddenInput />
{/* Other CascadeSelect components */}
</CascadeSelect.Root>
API Reference
Props
Root
| Prop | Default | Type |
|---|---|---|
collection | TreeCollection<T>The collection of cascade select nodes | |
allowParentSelection | booleanWhether parent (branch) items can be selectable | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
closeOnSelect | true | booleanWhether the cascade-select should close when an item is selected |
defaultHighlightedValue | string[]The initial highlighted value of the cascade-select when rendered. | |
defaultOpen | booleanThe initial open state of the cascade-select when rendered. Use when you don't need to control the open state. | |
defaultValue | string[][]The initial value of the cascade-select when rendered. Use when you don't need to control the value. | |
disabled | booleanWhether the cascade-select is disabled | |
form | stringThe form attribute of the underlying input element | |
formatValue | (selectedItems: T[][]) => stringFunction to format the display value | |
highlightedValue | string[]The controlled highlighted value of the cascade-select | |
highlightTrigger | 'click' | 'click' | 'hover'What triggers highlighting of items |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
control: string
trigger: string
indicator: string
clearTrigger: string
positioner: string
content: string
hiddenInput: string
list(valuePath: string): string
item(valuePath: string): string
}>The ids of the cascade-select elements. Useful for composition. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
invalid | booleanWhether the cascade-select is invalid | |
lazyMount | false | booleanWhether to enable lazy mounting |
loopFocus | false | booleanWhether the cascade-select should loop focus when navigating with keyboard |
multiple | false | booleanWhether to allow multiple selections |
name | stringThe name attribute of the underlying input element | |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
onFocusOutside | (event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component | |
onHighlightChange | (details: HighlightChangeDetails<T>) => voidCalled when the highlighted value changes | |
onInteractOutside | (event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => voidCalled when the open state changes | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component | |
onValueChange | (details: ValueChangeDetails<T>) => voidCalled when the value changes | |
open | booleanThe controlled open state of the cascade-select | |
positioning | PositioningOptionsThe positioning options for the cascade-select content | |
present | booleanWhether the node is present (controlled by the user) | |
readOnly | booleanWhether the cascade-select is read-only | |
required | booleanWhether the cascade-select is required | |
scrollToIndexFn | (details: ScrollToIndexDetails) => voidFunction to scroll to a specific index in a list | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
value | string[][]The controlled value of the cascade-select |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | root |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
[data-state] | "open" | "closed" |
ClearTrigger
Renders a <button> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | clear-trigger |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
Content
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | content |
[data-activedescendant] | The id the active descendant of the content |
[data-state] | "open" | "closed" |
| CSS Variable | Description |
|---|---|
--layer-index | The index of the dismissable in the layer stack |
--nested-layer-count | The number of nested cascade-selects |
Control
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | control |
[data-disabled] | Present when disabled |
[data-focused] | |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
[data-state] | "open" | "closed" |
HiddenInput
Renders a <input> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Indicator
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | indicator |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
ItemIndicator
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | item-indicator |
[data-value] | The value of the item |
[data-highlighted] | Present when highlighted |
[data-type] | The type of the item |
[data-state] | "checked" | "unchecked" |
Item
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
indexPath | IndexPathThe index path of the item | |
value | string[]The value path of the item | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
item | anyThe item to render |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | item |
[data-value] | The value of the item |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
[data-selected] | Present when selected |
[data-depth] | The depth of the item |
[data-state] | "checked" | "unchecked" |
[data-type] | The type of the item |
[data-index-path] |
ItemText
Renders a <span> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | item-text |
[data-value] | The value of the item |
[data-highlighted] | Present when highlighted |
[data-state] | "checked" | "unchecked" |
[data-disabled] | Present when disabled |
Label
Renders a <label> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | label |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
List
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
indexPath | IndexPathThe index path of the item | |
value | string[]The value path of the item | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
item | anyThe item to render |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | list |
[data-depth] | The depth of the item |
Positioner
Renders a <div> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--reference-width | The width of the reference element |
--reference-height | The height of the root |
--available-width | The available width in viewport |
--available-height | The available height in viewport |
--x | The x position for transform |
--y | The y position for transform |
--z-index | The z-index value |
--transform-origin | The transform origin for animations |
RootProvider
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | booleanWhether to enable lazy mounting |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
present | booleanWhether the node is present (controlled by the user) | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
value | any |
Trigger
Renders a <button> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | trigger |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
[data-invalid] | Present when invalid |
[data-focused] | |
[data-placement] | The placement of the trigger |
ValueText
Renders a <span> element.
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
placeholder | stringText to display when no value is selected. |
| Attribute | Description |
|---|---|
[data-scope] | cascade-select |
[data-part] | value-text |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-focused] | Present when focused |
Context
API
| Property | Type |
|---|---|
collection | TreeCollection<V>The tree collection data |
open | booleanWhether the cascade-select is open |
focused | booleanWhether the cascade-select is focused |
multiple | booleanWhether the cascade-select allows multiple selections |
disabled | booleanWhether the cascade-select is disabled |
highlightedValue | string[]The value of the highlighted item |
highlightedItems | V[]The items along the highlighted path |
selectedItems | V[][]The selected items |
hasSelectedItems | booleanWhether there's a selected option |
empty | booleanWhether the cascade-select value is empty |
value | string[][]The current value of the cascade-select |
valueAsString | stringThe current value as text |
focus | () => voidFunction to focus on the select input |
reposition | (options?: Partial<PositioningOptions>) => voidFunction to set the positioning options of the cascade-select |
setOpen | (open: boolean) => voidFunction to open the cascade-select |
setHighlightValue | (value: string | string[]) => voidFunction to set the highlighted value (path or single value to find) |
clearHighlightValue | () => voidFunction to clear the highlighted value |
selectValue | (value: string[]) => voidFunction to select a value |
setValue | (value: string[][]) => voidFunction to set the value |
clearValue | (value?: string[]) => voidFunction to clear the value |
getItemState | (props: ItemProps<V>) => ItemState<V>Returns the state of a cascade-select item |
getValueTextProps | () => T["element"]Returns the props for the value text element |
Accessibility
Keyboard Support
| Key | Description |
|---|---|
Space | When focus is on trigger, opens the cascade select and focuses the first item. When focus is on the content, selects the highlighted item. |
Enter | When focus is on trigger, opens the cascade select and focuses the first item. When focus is on content, selects the highlighted item. |
ArrowDown | When focus is on trigger, opens the cascade select. When focus is on content, moves focus to the next item in the current level. |
ArrowUp | When focus is on trigger, opens the cascade select and focuses the last item. When focus is on content, moves focus to the previous item in the current level. |
ArrowRight | When focus is on a branch item, expands the next level and moves focus into it. |
ArrowLeft | When focus is on a nested level, collapses it and moves focus back to the parent. When focus is at the root level, closes the cascade select. |
Home | Moves focus to the first item in the current level. |
End | Moves focus to the last item in the current level. |
Esc | Closes the cascade select and moves focus to trigger. |