# 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
Location
```
#### 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
Location
```
#### 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 (
<>
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 (
<>
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
Location
```
#### Svelte
```svelte
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
Locations
```
#### 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
Location
```
#### 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
Category
```
#### 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
Location
```
#### 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