# Cascade Select URL: https://ark-ui.com/docs/components/cascade-select Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/cascade-select.mdx Displays nested options in cascading dropdown panels. --- ## Anatomy ```tsx ``` ## Examples **Example: basic** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const Basic = () => ( Location ) const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, { label: 'Germany', value: 'germany', children: [ { label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const Basic = () => ( Location ) const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, { label: 'Germany', value: 'germany', children: [ { label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte Location {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### Controlled Use the `value` and `onValueChange` props to control the selected value. **Example: controlled** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import { useState } from 'react' import styles from 'styles/cascade-select.module.css' export const Controlled = () => { const [value, setValue] = useState([]) return ( setValue(details.value)} > Location ) } const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show, createSignal } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const Controlled = () => { const [value, setValue] = createSignal([]) return ( setValue(details.value)} > Location ) } const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte { value = details.value }} > Location {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], nodeValue: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value: nodeValue })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...nodeValue, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### 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. **Example: root-provider** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelect, useCascadeSelectContext, } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const RootProvider = () => { const api = useCascadeSelect({ collection }) return ( <> value: {JSON.stringify(api.value)} Location ) } const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelect, useCascadeSelectContext, } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const RootProvider = () => { const api = useCascadeSelect({ collection }) return ( <> value: {JSON.stringify(api().value)} Location ) } const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte value: {JSON.stringify(cascadeSelect().value)} Location {@render renderNode(cascadeSelect, collection.rootNode, [], [])} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### Multiple Enable multiple selection with the `multiple` prop. Users can select more than one leaf value. **Example: multiple** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const Multiple = () => ( Locations ) const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, { label: 'Germany', value: 'germany', children: [ { label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const Multiple = () => ( Locations ) const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte Locations {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### Hover Trigger Use `highlightTrigger="hover"` to highlight items on hover instead of requiring keyboard navigation or click. **Example: hover-trigger** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const HoverTrigger = () => ( Location ) const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, { label: 'Canada', value: 'canada', children: [ { label: 'Ontario', value: 'ontario' }, { label: 'Quebec', value: 'quebec' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, { label: 'Germany', value: 'germany', children: [ { label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const HoverTrigger = () => ( Location ) const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte Location {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### Allow Parent Selection By default, only leaf nodes can be selected. Use `allowParentSelection` to allow branch nodes to be selected as well. **Example: allow-parent-selection** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const AllowParentSelection = () => ( Category ) const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } 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', children: [ { label: 'Android', value: 'android' }, { label: 'iPhone', value: 'iphone' }, ], }, { label: 'Laptops', value: 'laptops', children: [ { label: 'Gaming', value: 'gaming-laptop' }, { label: 'Ultrabook', value: 'ultrabook' }, ], }, ], }, { label: 'Clothing', value: 'clothing', children: [ { label: 'Shirts', value: 'shirts' }, { label: 'Pants', value: 'pants' }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const AllowParentSelection = () => ( Category ) const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } 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', children: [ { label: 'Android', value: 'android' }, { label: 'iPhone', value: 'iphone' }, ], }, { label: 'Laptops', value: 'laptops', children: [ { label: 'Gaming', value: 'gaming-laptop' }, { label: 'Ultrabook', value: 'ultrabook' }, ], }, ], }, { label: 'Clothing', value: 'clothing', children: [ { label: 'Shirts', value: 'shirts' }, { label: 'Pants', value: 'pants' }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte Category {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ### Events Use `onValueChange`, `onHighlightChange`, and `onOpenChange` to respond to state changes. **Example: events** #### React ```tsx import { Portal } from '@ark-ui/react/portal' import { CascadeSelect, createCascadeCollection, useCascadeSelectContext } from '@ark-ui/react/cascade-select' import type { CascadeSelectHighlightChangeDetails, CascadeSelectValueChangeDetails } from '@ark-ui/react/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-react' import styles from 'styles/cascade-select.module.css' export const Events = () => { const handleValueChange = (details: CascadeSelectValueChangeDetails) => { console.log('value changed:', details.value, details.items) } const handleHighlightChange = (details: CascadeSelectHighlightChangeDetails) => { console.log('highlight changed:', details.highlightedValue, details.highlightedItems) } const handleOpenChange = (details: { open: boolean }) => { console.log('open changed:', details.open) } return ( Location ) } const TreeNode = ({ node, indexPath = [], value = [] }: { node: Node; indexPath?: number[]; value?: string[] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Solid ```tsx import { CascadeSelect, createCascadeCollection, useCascadeSelectContext, type CascadeSelectHighlightChangeDetails, type CascadeSelectValueChangeDetails, } from '@ark-ui/solid/cascade-select' import { ChevronRightIcon, ChevronsUpDownIcon, XIcon } from 'lucide-solid' import { For, Show } from 'solid-js' import { Portal } from 'solid-js/web' import styles from 'styles/cascade-select.module.css' export const Events = () => { const handleValueChange = (details: CascadeSelectValueChangeDetails) => { console.log('value changed:', details.value, details.items) } const handleHighlightChange = (details: CascadeSelectHighlightChangeDetails) => { console.log('highlight changed:', details.highlightedValue, details.highlightedItems) } const handleOpenChange = (details: { open: boolean }) => { console.log('open changed:', details.open) } return ( Location ) } const TreeNode = (props: { node: Node; indexPath?: number[]; value?: string[] }) => { const context = useCascadeSelectContext() const nodeState = () => context().getItemState({ item: props.node, indexPath: props.indexPath ?? [], value: props.value ?? [] }) return ( <> {(child, getIndex) => ( {collection.stringifyNode(child)} ✓} > )} ) } interface Node { label: string value: string children?: Node[] } const collection = createCascadeCollection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, rootNode: { label: 'Root', value: 'root', children: [ { label: 'Americas', value: 'americas', children: [ { label: 'United States', value: 'us', children: [ { label: 'New York', value: 'new-york' }, { label: 'California', value: 'california' }, ], }, ], }, { label: 'Europe', value: 'europe', children: [ { label: 'France', value: 'france', children: [ { label: 'Paris', value: 'paris' }, { label: 'Lyon', value: 'lyon' }, ], }, ], }, ], }, }) ``` #### Vue ```vue ``` #### Svelte ```svelte Location {#snippet render(api)} {@render renderNode(api, collection.rootNode, [], [])} {/snippet} {#snippet renderNode(api: UseCascadeSelectReturn, node: Node, indexPath: number[], value: string[])} {@const nodeState = api().getItemState({ item: node, indexPath, value })} {#each collection.getNodeChildren(node) as child, i} {collection.stringifyNode(child)} {#if collection.isBranchNode(child)} {:else} {/if} {/each} {#if nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild)} {@render renderNode( api, nodeState.highlightedChild as Node, [...indexPath, nodeState.highlightedIndex], [...value, collection.getNodeValue(nodeState.highlightedChild as Node)], )} {/if} {/snippet} ``` ## 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. ```tsx 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. ```tsx const TreeNode = ({ node, indexPath = [], value = [] }) => { const api = useCascadeSelectContext() const nodeState = api.getItemState({ item: node, indexPath, value }) return ( <> {collection.getNodeChildren(node).map((child, i) => ( {collection.stringifyNode(child)} {collection.isBranchNode(child) ? ( ) : ( )} ))} {nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } ``` ### Hidden Input The `CascadeSelect.HiddenInput` component renders a native `` element that is visually hidden but present in the DOM, enabling native form submission with the selected value. ```tsx {/* Other CascadeSelect components */} ``` ## API Reference ### Props ### Context **API:** | Property | Type | Description | |----------|------|-------------| | `collection` | `TreeCollection` | The tree collection data | | `open` | `boolean` | Whether the cascade-select is open | | `focused` | `boolean` | Whether the cascade-select is focused | | `multiple` | `boolean` | Whether the cascade-select allows multiple selections | | `disabled` | `boolean` | Whether 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` | `boolean` | Whether there's a selected option | | `empty` | `boolean` | Whether the cascade-select value is empty | | `value` | `string[][]` | The current value of the cascade-select | | `valueAsString` | `string` | The current value as text | | `focus` | `() => void` | Function to focus on the select input | | `reposition` | `(options?: Partial) => void` | Function to set the positioning options of the cascade-select | | `setOpen` | `(open: boolean) => void` | Function to open the cascade-select | | `setHighlightValue` | `(value: string | string[]) => void` | Function to set the highlighted value (path or single value to find) | | `clearHighlightValue` | `() => void` | Function to clear the highlighted value | | `selectValue` | `(value: string[]) => void` | Function to select a value | | `setValue` | `(value: string[][]) => void` | Function to set the value | | `clearValue` | `(value?: string[]) => void` | Function to clear the value | | `getItemState` | `(props: ItemProps) => ItemState` | Returns the state of a cascade-select item | | `getValueTextProps` | `() => T["element"]` | Returns the props for the value text element | ## Accessibility ### Keyboard Support