Skip to content

Cascader

Cascade selection box for hierarchical data like locations or categories.

import { Cascader } from 'asterui'

Basic

Simple hierarchical selection.

import { Cascader } from 'asterui'

function App() {
  const options = [
    {
      value: 'usa',
      label: 'United States',
      children: [
        {
          value: 'ca',
          label: 'California',
          children: [
            { value: 'sf', label: 'San Francisco' },
            { value: 'la', label: 'Los Angeles' },
          ],
        },
        {
          value: 'ny',
          label: 'New York',
          children: [
            { value: 'nyc', label: 'New York City' },
            { value: 'buf', label: 'Buffalo' },
          ],
        },
      ],
    },
  ]
  
  return (
      <Cascader options={options} placeholder="Select location" />
    )
}

export default App

Hover Expand

Expand sub-menus on hover.

import { Cascader } from 'asterui'

function App() {
  const options = [
    {
      value: 'electronics',
      label: 'Electronics',
      children: [
        {
          value: 'phones',
          label: 'Phones',
          children: [
            { value: 'iphone', label: 'iPhone' },
            { value: 'android', label: 'Android' },
          ],
        },
        {
          value: 'computers',
          label: 'Computers',
          children: [
            { value: 'laptop', label: 'Laptop' },
            { value: 'desktop', label: 'Desktop' },
          ],
        },
      ],
    },
  ]
  
  return (
      <Cascader options={options} expandTrigger="hover" placeholder="Select category" />
    )
}

export default App

Sizes

Different cascader sizes.

import { Cascader } from 'asterui'

function App() {
  const options = [
    { value: 'opt1', label: 'Option 1' },
    { value: 'opt2', label: 'Option 2' },
  ]
  
  return (
      <div className="flex flex-col gap-2">
        <Cascader size="xs" options={options} placeholder="Extra small" />
        <Cascader size="sm" options={options} placeholder="Small" />
        <Cascader size="md" options={options} placeholder="Medium" />
        <Cascader size="lg" options={options} placeholder="Large" />
      </div>
    )
}

export default App

Disabled

Disabled cascader state.

import { Cascader } from 'asterui'

function App() {
  const options = [
    { value: 'opt1', label: 'Option 1' },
    { value: 'opt2', label: 'Option 2' },
  ]
  
  return (
      <Cascader options={options} disabled value={['opt1']} />
    )
}

export default App

Search

Enable search/filter functionality.

import { Cascader } from 'asterui'

function App() {
  const options = [
    {
      value: 'usa',
      label: 'United States',
      children: [
        {
          value: 'ca',
          label: 'California',
          children: [
            { value: 'sf', label: 'San Francisco' },
            { value: 'la', label: 'Los Angeles' },
          ],
        },
      ],
    },
  ]
  
  return (
      <Cascader options={options} showSearch placeholder="Search locations" />
    )
}

export default App

Change On Select

Allow selection of any level, not just leaf nodes.

Selected: None

import { Cascader } from 'asterui'
import { useState } from 'react'

function App() {
  const [value, setValue] = useState<string[]>([])
  const options = [
    {
      value: 'usa',
      label: 'United States',
      children: [
        { value: 'ca', label: 'California' },
        { value: 'ny', label: 'New York' },
      ],
    },
  ]
  
  return (
      <div>
        <Cascader
          options={options}
          changeOnSelect
          value={value}
          onChange={setValue}
          placeholder="Select any level"
        />
        <p className="mt-2 text-sm">Selected: {value.join(' / ') || 'None'}</p>
      </div>
    )
}

export default App

Async Loading

Load options dynamically when expanding.

import { Cascader } from 'asterui'
import { useState } from 'react'

function App() {
  const [options, setOptions] = useState<CascaderOption[]>([
    { value: 'region1', label: 'Region 1' },
    { value: 'region2', label: 'Region 2' },
  ])
  
  const loadData = async (selectedOptions: CascaderOption[]) => {
    const targetOption = selectedOptions[selectedOptions.length - 1]
    await new Promise(resolve => setTimeout(resolve, 1000))
  
    setOptions(prev => {
      const update = (opts: CascaderOption[]): CascaderOption[] =>
        opts.map(opt =>
          opt.value === targetOption.value
            ? {
                ...opt,
                children: [
                  { value: `${opt.value}-1`, label: 'Child 1', isLeaf: true },
                  { value: `${opt.value}-2`, label: 'Child 2', isLeaf: true },
                ],
              }
            : { ...opt, children: opt.children ? update(opt.children) : undefined }
        )
      return update(prev)
    })
  }
  
  return (
      <Cascader options={options} loadData={loadData} placeholder="Load on expand" />
    )
}

export default App

Validation Status

Show error or warning validation state.

import { Cascader } from 'asterui'

function App() {
  const options = [
    { value: 'opt1', label: 'Option 1' },
    { value: 'opt2', label: 'Option 2' },
  ]
  
  return (
      <div className="flex flex-col gap-2">
        <Cascader options={options} status="error" placeholder="Error state" />
        <Cascader options={options} status="warning" placeholder="Warning state" />
      </div>
    )
}

export default App

Multiple Selection

Select multiple leaf values.

import { Cascader } from 'asterui'

function App() {
  const options = [
    {
      value: 'fruits',
      label: 'Fruits',
      children: [
        { value: 'apple', label: 'Apple' },
        { value: 'banana', label: 'Banana' },
      ],
    },
    {
      value: 'vegetables',
      label: 'Vegetables',
      children: [
        { value: 'carrot', label: 'Carrot' },
        { value: 'lettuce', label: 'Lettuce' },
      ],
    },
  ]
  
  return (
      <Cascader options={options} multiple placeholder="Select multiple" />
    )
}

export default App
PropertyDescriptionTypeDefault
optionsHierarchical options dataCascaderOption[][]
valueControlled selected value pathstring[]-
defaultValueDefault selected value (uncontrolled)string[][]
onChangeCallback when selection changes(value: string[], selectedOptions: CascaderOption[]) => void-
placeholderInput placeholder textstring'Please select'
disabledDisable the cascaderbooleanfalse
allowClearShow clear buttonbooleantrue
expandTriggerHow to expand sub-menus'click' | 'hover''click'
changeOnSelectAllow selection of any level, not just leavesbooleanfalse
displayRenderCustom display render function(labels: ReactNode[], selectedOptions: CascaderOption[]) => ReactNode-
sizeInput size'xs' | 'sm' | 'md' | 'lg''md'
colorFocus ring color'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'-
statusValidation status'error' | 'warning'-
showSearchEnable search functionalityboolean | { filter?, render?, matchInputWidth? }false
notFoundContentContent when no search resultsReactNode'No results found'
loadDataAsync data loading function(selectedOptions: CascaderOption[]) => Promise<void>-
fieldNamesCustom field names for data mapping{ label?: string; value?: string; children?: string }-
openControlled dropdown open stateboolean-
onDropdownVisibleChangeCallback when dropdown visibility changes(open: boolean) => void-
popupClassNameClass name for dropdownstring-
dropdownRenderCustom dropdown wrapper render(menu: ReactNode) => ReactNode-
multipleEnable multiple selection modebooleanfalse
maxTagCountMax tags to show in multiple modenumber | 'responsive'-
classNameAdditional CSS classesstring-
PropertyDescriptionTypeDefault
valueUnique identifierstring-
labelDisplay labelReactNode-
childrenChild optionsCascaderOption[]-
disabledDisable this optionbooleanfalse
isLeafForce as leaf node (no expand, no async load)boolean-

The Cascader component follows WAI-ARIA combobox pattern:

  • Uses role="combobox" on trigger with aria-expanded, aria-haspopup
  • aria-activedescendant tracks focused option for screen readers
  • aria-selected indicates selected state on options
  • aria-disabled indicates disabled state
  • Clear button has aria-label="Clear selection"
KeyAction
DownOpen dropdown or move to next option
UpMove to previous option
RightMove to next column (expand)
LeftMove to previous column
EnterSelect focused option or open dropdown
SpaceToggle dropdown (when not searching)
EscapeClose dropdown
HomeMove to first option
EndMove to last option

The component includes data attributes for testing:

  • data-testid="cascader" on the root container
  • data-testid="cascader-dropdown" on the dropdown
  • data-testid="cascader-option-{value}" on each option
  • data-testid="cascader-clear" on the clear button
  • data-state="open|closed" on root indicates dropdown state
  • data-state="selected|hovered" on options indicates state
  • data-value="{value}" on options provides the value