---
title: "Cascader"
description: "A selector that picks one or more paths from hierarchical data."
category: Form
syncStatus: reviewed
status: beta
since: 0.3.4
tags: [cascader, form, hierarchy]
verified: true
---

## Usage

### Single and Multiple
A single selection is one path array; with `multiple`, the value is an array of paths.
:::TuffDemoWrapper{demo="CascaderCascaderDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const single = ref()
  const paths = ref([])

  const options = [
    {
      value: 'zhejiang',
      label: 'Zhejiang',
      children: [
        {
          value: 'hangzhou',
          label: 'Hangzhou',
          children: [
            { value: 'xihu', label: 'West Lake', leaf: true },
            { value: 'binjiang', label: 'Binjiang', leaf: true },
          ],
        },
      ],
    },
  ]
  </script>

  <template>
    <TxCascader v-model="single" :options="options" placeholder="Single" />
    <TxCascader v-model="paths" :options="options" multiple placeholder="Multiple" />
  </template>
---
:::

### Release Policy
An admin flow: Cascader sets the scope, FlatSelect the policy, sliders the thresholds, and TagInput the labels.
::TuffDemoWrapper{demo="ComponentsReleasePolicyDemo" code-lang="vue"}
---
code: |
  <template>
    <TxCascader v-model="releasePath" :options="scopeOptions" placeholder="Release scope" />
    <TxFlatSelect v-model="rolloutMode" placeholder="Rollout mode">
      <TxFlatSelectItem value="phased" label="Phased" />
      <TxFlatSelectItem value="guarded" label="Guarded" />
    </TxFlatSelect>
    <TxSegmentedSlider v-model="riskLevel" :segments="riskSegments" />
    <TxSlider v-model="traffic" :min="5" :max="100" :step="5" show-value />
    <TxTagInput v-model="labels" placeholder="Press Enter to add tags" :max="5" />
  </template>
---
::

### Best Practices

- Keep hierarchies to two or three levels; use search or TreeSelect for deeper trees.
- Keep `value` keys stable across releases: the value stores path keys, so renaming one breaks saved selections.
- Mark terminal async nodes `leaf: true`; otherwise they call `load` instead of becoming selectable.
- Persist full paths, not leaf ids, so labels can be rebuilt.
- Keep a visible field label; don't rely on the placeholder.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `modelValue` | `CascaderValue` | - | One path in single mode; an array of paths in multiple mode. |
| `options` | `CascaderNode[]` | `[]` | The root option tree. |
| `multiple` | `boolean` | `false` | Selects multiple leaf paths, shown as tags. |
| `disabled` | `boolean` | `false` | Blocks opening, clearing, and selection. |
| `placeholder` | `string` | `'Please select'` | Text shown when nothing is selected. |
| `searchable` | `boolean` | `true` | Shows a search box that matches loaded leaf paths only. |
| `clearable` | `boolean` | `true` | Shows a clear button when enabled and a value is set. |
| `placement` | `PopoverPlacement` | `'bottom-start'` | Root panel placement, forwarded to `TxPopover`. |
| `dropdownOffset` | `number` | `6` | Root panel offset, forwarded to `TxPopover`. |
| `dropdownWidth` | `number` | `260` | Root panel width; child panels fit their content between 200px and `dropdownMaxWidth`. |
| `dropdownMaxWidth` | `number` | `520` | Maximum panel width. |
| `dropdownMaxHeight` | `number` | `340` | Maximum panel height in px. |
| `expandTrigger` | `'click' \| 'hover' \| 'both'` | `'both'` | Branch panels open on click with `click`, on hover otherwise; clicking a branch row always expands it. |
| `load` | `(node, level) => Promise<CascaderNode[]>` | - | Loads children for a node that has no `children` and is not a `leaf`. |

### Events

| Event | Params | Description |
|------|------|------|
| `update:modelValue` | `(v)` | Fires with the new value after a selection or clear. |
| `change` | `(v)` | Fires together with `update:modelValue`. |
| `open` | - | Fires after the dropdown opens. |
| `close` | - | Fires after the dropdown closes. |
| `load-error` | `({ path: CascaderPath, error: unknown })` | Fires when `load` rejects, with the node's path; expanding it again retries. |

### Exposed Methods

| Name | Type | Description |
|------|------|------|
| `open()` | `() => void` | Opens the dropdown. |
| `close()` | `() => void` | Closes the dropdown. |
| `toggle()` | `() => void` | Toggles the dropdown. |
| `focus()` | `() => void` | Focuses the trigger. |
| `blur()` | `() => void` | Blurs the trigger. |
| `clear()` | `() => void` | Clears to `undefined` in single mode or `[]` in multiple mode. |
| `setValue(v)` | `(v) => void` | Emits `update:modelValue` and `change` with the given value. |
| `getValue()` | `() => any` | Returns the current `modelValue`. |

### Types

:::TuffCodeBlock{lang="ts"}
---
code: |
  type CascaderPath = Array<string | number>
  type CascaderValue = CascaderPath | CascaderPath[] | undefined // single | multiple
---
:::

`CascaderNode`, one entry of `options`:

| Field | Type | Description |
|------|------|------|
| `value` | `string \| number` | Node key; paths are built from these. |
| `label` | `string` | Text shown in rows, tags, and search results. |
| `disabled` | `boolean` | Prevents expanding or selecting the node. |
| `leaf` | `boolean` | Marks a selectable end node and skips lazy loading. |
| `children` | `CascaderNode[]` | Child nodes, rendered in the panel anchored to this row. |

## Overview

- Each level is its own floating panel, anchored `right-start` to the row that opened it and sized to its labels; only opened branches are mounted.
- Hover travel, the outside-click exemption, and cascading close run on the anchor-delay service, as in `TxDropdownSubmenu`, safe triangle included.
- The trigger is `role="combobox"`; each level is a `role="listbox"` of `role="option"` rows, and branch rows add `aria-haspopup="listbox"` and `aria-expanded`.
- Keyboard: Up/Down move and wrap, Home/End jump to the ends, ArrowRight opens a branch and focuses its first row, ArrowLeft closes the level and returns focus. ArrowLeft at the root is not intercepted.
- Every row on the trail to the open panel stays active.
- A search query replaces the levels with a flat list of leaf paths; clearing it restores them.

## Technologies

- Source: `packages/tuffex/packages/components/src/cascader/`.

<TuffDocSourceLink />
