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
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. |