Vize

Cascader

Accessible, unstyled Cascader: typed multi-level option paths in adjacent listbox columns.

Package @vizejs/ui/cascader
Maturity stable
Own the source vize lib pull cascader
Requires context, controllable-state, dismissable-layer, id, portal, positioner, presence
Aliases cascader, cascade select, multi-level select, hierarchical picker
Covers Ant Design Cascader, Element Plus Cascader, PrimeVue CascadeSelect

Usage

import { Cascader, CascaderRoot, CascaderColumn, CascaderContent, CascaderItem, CascaderTrigger, CascaderValue } from "@vizejs/ui/cascader";

Or copy the source into your project with vize lib pull cascader (see Source Distribution).

API

Cascader<T, Multiple extends boolean = false>

Source: src/families/selection/cascader/cascader-root.vue

Props

Prop Type Default Description
id string | null undefined Consumer-owned base id. null and undefined select a deterministic fallback.
options (required) readonly T[] required Top-level options.
modelValue CascaderModelValue<T, Multiple> undefined Controlled selection: one path, or a list of leaf paths when multiple is true.
defaultValue CascaderModelValue<T, Multiple> undefined Initial selection for uncontrolled use and the value restored by reset.
multiple Multiple false Select several leaf paths. The literal type decides the model type.
getChildren (node: T) => readonly T[] | undefined undefined Child accessor. Defaults to an array-valued children property.
isLeaf (node: T) => boolean undefined Leaf predicate. Defaults to "has no children and nothing to load".
loadChildren (node: T, context: CascaderLoadContext) => Promise<readonly T[]> undefined Lazily load a branch's children the first time it expands.
by CascaderBy<T> undefined Compare nodes by a property key or with a custom equality function.
itemText (node: T) => string undefined Human-readable text for a node, used by the value display, typeahead, and search.
itemDisabled (node: T) => boolean undefined Disable individual nodes.
formValue (node: T) => string undefined Serialize one node for form submission; path segments are joined by separator.
changeOnSelect boolean false Allow choosing branch options, not only leaves (single mode).
expandTrigger CascaderExpandTrigger "click" Expand branches on click or on pointer hover.
separator string " / " Separator between path segments in display text and form values.
search string undefined Controlled search text; a non-empty query publishes searchResults.
open boolean undefined Controlled open state. undefined selects uncontrolled behavior.
defaultOpen boolean false Initial open state for uncontrolled use.
disabled boolean false Disable the trigger and every option.
required boolean false Mark a selection as required for assistive technology.
name string undefined Form field name; each selected path submits one hidden input.
form string undefined Id of the owning form when rendered outside it.
placeholder string undefined Text shown by CascaderValue while nothing is selected.
typeaheadTimeout number 500 Idle time before buffered typeahead starts a new query.

Events

Event Payload Description
update:modelValue [value: CascaderModelValue<T, Multiple>] Fired when the selection requests a new controlled value (a path, or a list of paths).
change [ value: CascaderModelValue<T, Multiple>, previous: CascaderModelValue<T, Multiple>, nativeEvent: Event | null, ] Fired after a user interaction changes the selection: next value, previous value, native event.
update:open [open: boolean] Fired when the popup requests a controlled open value.
update:search [query: string] Fired when the search text requests a new controlled value.
load-error [node: T, error: unknown] Fired when loadChildren rejects for a branch, with the error.

Slots

Slot Slot props Description
default CascaderSlotState<T> Trigger, value, and popup parts. Receives columns, selection, and search results.

Exposed

Member Type Description
clear —
expandPath —
focus —
open —
reset —
selected —
selectPath —
setOpen —

CascaderRoot<T, Multiple extends boolean = false>

Source: src/families/selection/cascader/cascader-root.vue

Props

Prop Type Default Description
id string | null undefined Consumer-owned base id. null and undefined select a deterministic fallback.
options (required) readonly T[] required Top-level options.
modelValue CascaderModelValue<T, Multiple> undefined Controlled selection: one path, or a list of leaf paths when multiple is true.
defaultValue CascaderModelValue<T, Multiple> undefined Initial selection for uncontrolled use and the value restored by reset.
multiple Multiple false Select several leaf paths. The literal type decides the model type.
getChildren (node: T) => readonly T[] | undefined undefined Child accessor. Defaults to an array-valued children property.
isLeaf (node: T) => boolean undefined Leaf predicate. Defaults to "has no children and nothing to load".
loadChildren (node: T, context: CascaderLoadContext) => Promise<readonly T[]> undefined Lazily load a branch's children the first time it expands.
by CascaderBy<T> undefined Compare nodes by a property key or with a custom equality function.
itemText (node: T) => string undefined Human-readable text for a node, used by the value display, typeahead, and search.
itemDisabled (node: T) => boolean undefined Disable individual nodes.
formValue (node: T) => string undefined Serialize one node for form submission; path segments are joined by separator.
changeOnSelect boolean false Allow choosing branch options, not only leaves (single mode).
expandTrigger CascaderExpandTrigger "click" Expand branches on click or on pointer hover.
separator string " / " Separator between path segments in display text and form values.
search string undefined Controlled search text; a non-empty query publishes searchResults.
open boolean undefined Controlled open state. undefined selects uncontrolled behavior.
defaultOpen boolean false Initial open state for uncontrolled use.
disabled boolean false Disable the trigger and every option.
required boolean false Mark a selection as required for assistive technology.
name string undefined Form field name; each selected path submits one hidden input.
form string undefined Id of the owning form when rendered outside it.
placeholder string undefined Text shown by CascaderValue while nothing is selected.
typeaheadTimeout number 500 Idle time before buffered typeahead starts a new query.

Events

Event Payload Description
update:modelValue [value: CascaderModelValue<T, Multiple>] Fired when the selection requests a new controlled value (a path, or a list of paths).
change [ value: CascaderModelValue<T, Multiple>, previous: CascaderModelValue<T, Multiple>, nativeEvent: Event | null, ] Fired after a user interaction changes the selection: next value, previous value, native event.
update:open [open: boolean] Fired when the popup requests a controlled open value.
update:search [query: string] Fired when the search text requests a new controlled value.
load-error [node: T, error: unknown] Fired when loadChildren rejects for a branch, with the error.

Slots

Slot Slot props Description
default CascaderSlotState<T> Trigger, value, and popup parts. Receives columns, selection, and search results.

Exposed

Member Type Description
selected readonly (readonly T[])[] Selected paths.
open boolean Whether the popup is open.
setOpen (open: boolean) => boolean Request a specific open state.
selectPath (path: readonly T[]) => boolean Choose a path (toggle in multiple mode).
expandPath (path: readonly T[]) => void Expand the branch at the end of path, loading it when needed.
clear () => boolean Clear the selection.
reset () => boolean Restore defaultValue.
focus (options?: FocusOptions) => void Focus the trigger.

CascaderColumn

Source: src/families/selection/cascader/cascader-column.vue

Props

Prop Type Default Description
level (required) number required Zero-based depth of the options this column lists.
ariaLabel string undefined Accessible name; defaults to the parent option's text (or "Options" at the top level).

Slots

Slot Slot props Description
default CascaderColumnSlotState CascaderItem options for this level; render them from the root columns state.

CascaderContent

Source: src/families/selection/cascader/cascader-content.vue

Props

Prop Type Default Description
placement Placement "bottom-start" Preferred placement relative to the trigger before collision handling.
strategy PositionerStrategy "fixed" CSS positioning mode published on the floating host.
offset number 4 Gap between the trigger and the popup.
collisionPadding number 8 Viewport padding the popup should not cross.
flip boolean true Flip to the opposite side when the preferred side overflows more.
shift boolean true Shift the popup back into the viewport.
size boolean true Publish available-size CSS variables and constrain the host.
to string | HTMLElement "body" CSS selector or element the popup is moved into.
portalDisabled boolean false Render in place instead of teleporting.
defer boolean true Keep content in place until the portal target exists, avoiding SSR mismatch.
forceMount boolean false Keep items mounted (hidden) while closed.
ariaLabel string undefined Accessible name of the popup region holding the columns.

Events

Event Payload Description
escape-key-down [event: DismissableLayerEscapeKeyDownEvent] Fired before Escape closes the popup. Call preventDefault() to keep it open.
pointer-down-outside [event: DismissableLayerPointerDownOutsideEvent] Fired before an outside pointer-down closes the popup. Call preventDefault() to keep it open.
dismiss [event: DismissableLayerDismissEvent] Fired after an unprevented dismissal request closes the popup.

Slots

Slot Slot props Description
default CascaderContentSlotState One CascaderColumn per entry of the root columns. Receives open state and placement.

Exposed

Member Type Description
element Readonly<ShallowRef<HTMLElement | null>> Template ref to the rendered root element.

CascaderItem<T>

Source: src/families/selection/cascader/cascader-item.vue

Props

Prop Type Default Description
value (required) T required Node rendered by this option; must belong to the column's options.
ariaLabel string undefined Accessible name when the option text is not enough.

Slots

Slot Slot props Description
default CascaderItemSlotState<T> Option content. Receives branch, expansion, selection, and loading state.

CascaderTrigger

Source: src/families/selection/cascader/cascader-trigger.vue

Props

Prop Type Default Description
ariaLabel string undefined Accessible name when no visible label supplies one.
ariaLabelledby string undefined Space-separated ids of visible labels.
ariaDescribedby string undefined Space-separated ids that describe the cascader.

Slots

Slot Slot props Description
default { readonly open: boolean; readonly empty: boolean } Trigger content, usually CascaderValue. Receives open and emptiness state.

Exposed

Member Type Description
element Readonly<ShallowRef<HTMLElement | null>> Template ref to the rendered root element.
focus —

CascaderValue

Source: src/families/selection/cascader/cascader-value.vue

Props

Prop Type Default Description
pathSeparator string ", " Text placed between paths when several are selected.

Slots

Slot Slot props Description
default CascaderValueSlotState Custom rendering of the selected paths.
placeholder CascaderValueSlotState Custom placeholder rendering while nothing is selected.

Behavior

Contract

Cascader is a headless, strongly typed multi-level picker. cascader-root.vue is generic over the node type T and a Multiple literal: the model is one option path (readonly T[], empty when nothing is selected) or, with multiple: true, a list of leaf paths (readonly (readonly T[])[]). Children come from getChildren (default: an array children property) or lazily from loadChildren(node, { signal }); nodes compare through by.

cascader-trigger.vue renders a native <button role="combobox" aria-haspopup="listbox"> that keeps DOM focus and exposes the highlighted option through aria-activedescendant; aria-controls lists every open column. cascader-content.vue renders the popup through Portal, Presence, Positioner, and a dismissable layer. The root publishes typed columns (the top level plus one per expanded branch); each cascader-column.vue is a role="listbox" labelled by its parent option and each cascader-item.vue a role="option" with aria-expanded on branches. cascader-value.vue renders the selected path text or a placeholder. Named cascaders submit one hidden input per selected path (formValue/by segments joined by separator). Option ids derive from the root id plus level and index. No CSS ships.

Normative Behavior

# State Input Outcome Proven by
K1 closed render combobox trigger with aria-haspopup="listbox", aria-expanded="false", placeholder text, no columns renders a combobox trigger with placeholder and no popup while closed
K2 open click branch / click leaf branches open child columns labelled by their parent and set aria-expanded; a leaf selects its path, closes, and shows A / B / C click expands branches into new columns and selecting a leaf closes
K3 selected reopen expands the selected path, highlights its last node, marks ancestors data-state="partial" reopening restores the selected path and marks ancestors as partial
K4 closed / open ArrowDown / Up / Home / End / ArrowRight / Enter / ArrowLeft / Space opens; moves within a column skipping disabled nodes; ArrowRight and Enter on a branch descend to its first option; ArrowLeft returns to the parent and closes deeper columns; Enter/Space on a leaf selects and closes keyboard matrix: open, arrows skip disabled, right/left traverse levels, Enter selects
K5 open printable keys / Escape typeahead per column (navigation keys reset the query); Escape closes without selecting typeahead moves within the active column and Escape closes without selecting
K6 changeOnSelect click branch commits the branch path and keeps the popup open changeOnSelect commits branch paths while keeping the popup open
K7 multiple click leaves toggles leaf paths, stays open, marks columns aria-multiselectable, submits one hidden input per path multiple mode toggles leaf paths and stays open
K8 expandTrigger=hover pointer move over a branch highlights and expands it hover expandTrigger expands branches on pointer movement
L1 loadChildren expand / expand a sibling / resolve loads once, marks the option data-loading and its column aria-busy, aborts a superseded sibling load, descends after a keyboard-triggered load loadChildren loads lazily, marks loading, descends by keyboard, and aborts superseded loads
L2 loadChildren reject / unmount emits load-error(node, error); unmount aborts pending loads unmount aborts pending loads and rejected loads emit load-error
S1 search non-empty query publishes matching (loaded) leaf paths as searchResults; selectPath chooses one search publishes matching paths that can be selected
C1 controlled / disabled select / keys controlled values emit and wait for the parent; disabled cascaders never open controlled values wait for the parent; disabled cascaders never open
C2 no provider mount a part throws VIZE_UI_CONTEXT_MISSING parts require a Cascader provider
U1 pure helpers findCascaderPath / flattenCascaderPaths / searchCascaderPaths / toCascaderSelection resolve, flatten (optionally including branches), search accent-insensitively, and normalize models pure helpers resolve, flatten, search, and normalize paths

SSR

Ids derive from the deterministic-id primitive, the popup renders in place until the portal hydrates, and lazy loads only start after mount. Closed and open trees render byte-identical markup and hydrate without warnings (renders byte-identical closed Cascader markup with the selected path label, renders byte-identical open Cascader markup with columns for the selected path, hydrates closed and open Cascaders without mismatches).