Cascader
Multi-level linked selection: multiple checkboxes, parent-child decoupled checking, value strategies, path search, lazy loading, controlled open and tag collapsing. Fully keyboard operable.
Basic Usage
Basic usage
Click to open the multi-level panel and drill down level by level until a leaf node is submitted.
Select & Submit
Select to submit (change-on-select)
With change-on-select enabled, clicking an option at any level (including nodes with children) immediately submits the current path.
Preset Path
Preset value (path array)
value is a JSON array holding the selected value of each level; it is displayed joined as "Zhejiang / Hangzhou".
Multiple
Multiple selection (multiple)
In multiple mode value is a JSON array of path arrays. Each panel row has a checkbox: checking a parent cascades to all children, and a partially checked parent shows an indeterminate state. The trigger echoes selections as removable tags; the panel stays open after checking for consecutive picks.
Check Strictly
Parent-child decoupled (check-strictly)
With check-strictly, parent and child checks are independent: checking a parent submits only the parent path, and the value is exactly the checked set (value-mode has no effect in this mode).
Value Mode
Value strategy (value-mode)
value-mode decides which paths are submitted in multiple mode: all (default) = every checked node (a parent enters the value once all its children are checked, consistent with the single-select full-path model); parentFirst = fully checked children collapse into the parent; onlyLeaf = only leaf paths. Echo tags mirror the literal value (host presets are never rewritten); interactions converge per strategy on submit.
Filterable
Searchable (filterable)
With filterable, a search box appears when the panel opens. Typing matches against the joined labels of every level and results are a flat path list; clicking a result selects that full path (checks it in multiple mode). An oas-search event is emitted while typing, and a custom matcher can be provided via el.filter = (query, path) => boolean (path is the array of option objects along the path).
Lazy Loading
Lazy loading (lazy + el.load)
After setting el.load = ({ option, path, depth }) => Promise<options>, expanding a not-yet-loaded node shows a loading state and fetches its children (option is null for the root level, so the first level can be lazy too). Options may declare isLeaf: true as leaves. This demo simulates a 500ms delay.
Hover Expand
Hover expand (expand-trigger=hover)
With expand-trigger="hover", hovering a parent option (after a ~120ms anti-mistap delay) expands its child column; clicking a leaf submits. Default is click.
Large Datasets (virtual scrolling)
Virtual scrolling (1000-item column)
With virtual, columns above the threshold (50 items) render only the visible window (reusing oas-virtual-list window math with top/bottom padding holding the scroll height) — thousands of options scroll smoothly and the panel height stays constant; item-height tunes the fixed row height (default 36). While navigating with ↑/↓ the window follows the highlighted row; short columns below the threshold, search results, and loading placeholders keep normal rendering.
Last Level & Separator
Last level only (show-all-levels) and separator
show-all-levels defaults to true (full path); setting it to false shows only the last label in the trigger. separator customizes the path separator (default /), also applied in search result rows.
Clearable
Clearable (clearable)
A clear button appears when a value exists; clicking it emits oas-clear (detail is the previous value) and oas-change (empty value).
Tag Collapsing
Tag collapsing (max-tag-count)
Tags wrap by default; explicitly setting max-tag-count folds them into +N by count (hover lists the hidden items), consistent with oas-select.
Size & Status
Size
Validation status
size supports small / medium / large (inheriting the nearest oas-config-provider global size); status supports error / warning / success (error syncs aria-invalid on the trigger; forms may also set host-level aria-invalid directly).
Controlled Open
Controlled open (open + oas-open-change)
The open attribute is the single source of truth for the expanded state (controlled): open/close from inside or outside is written back to it, and oas-open-change (detail.open) is emitted on every flip; hosts may intercept the event to take full control. Esc closes and returns focus to the trigger.
Empty
Empty state
When options are empty (and el.load is not set) the panel shows the empty placeholder; a search with no matches shows the no-match placeholder.
Loading state (loading)
Loading state (loading)
While loading is set the trigger shows a spinner with aria-busy (replacing the chevron); an open panel only shows the loading placeholder without rendering options — ideal while the host fetches remote cascading data.
Field mapping (field-names)
Field mapping (field-names)
field-names is a JSON field alias ({ label, value, children, disabled }, matching the oas-tree-select contract); the component reads your raw data through the aliases (isLeaf always reads its original key). Data returned from lazy el.load goes through the same mapping. In the example above "Design / UI" is disabled via the off field.
Disabled
Disabled
Accessible Name (label)
label (accessible name)
label is the accessible name (aria-label) of the trigger: screen readers announce it and it takes precedence over the selected value text and the placeholder; without it the component falls back to value text / placeholder (matching the oas-select contract).
Placement (placement)
placement (12 directions)
placement supports 12 directions: top / bottom / left / right × start / end / centered (e.g. top-start, right-end), defaulting to bottom (opens below, auto-flips when space runs out); invalid values fall back silently. Declare the direction directly for edge-adjacent scenarios such as sidebars or inside tables.
Custom Suffix Icon (suffix-icon slot)
suffix-icon (replaces the default arrow)
template[slot="suffix-icon"] replaces the default dropdown arrow on the trigger (rotating with the container when expanded; the spinner still takes over during loading).
Custom Option Rendering (option slot)
option (custom option rows)
template[slot="option"] is cloned into every option row (both panel rows and search-result rows); the [data-option-label] node is bound to the display text (the option label for panel rows, the full path text for search rows). Hosts can also rewrite row content via render events.
Events
Selection events
Listen to oas-change; detail.value is a path array in single mode, or an array of path arrays in multiple mode:
Focus Events (oas-focus / oas-blur)
focus / blur events
Trigger focus/blur dispatches oas-focus / oas-blur (detail.value shares the oas-change shape: a path array in single mode, an array of path arrays in multiple mode). Internal focus transfers between the trigger, search box, and chip buttons do not cause false reports.
Mobile form (bottom sheet)
On touch screens (coarse pointer) or narrow viewports (<768px), the multi-level panels are automatically hosted by an oas-bottom-sheet bottom sheet: rises from the viewport bottom + backdrop + drag handle swipe-down to close (backdrop click / Esc also close), with bottom safe-area inset; panel columns scroll horizontally inside the sheet; the desktop form keeps the original floating dropdown.
API
oas-cascader
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
change-on-select | Submit when selecting any level | boolean | — |
check-strictly | Decouple parent/child checking in multi-select (checking a parent does not cascade) | boolean | — |
clearable | Clearable (fires oas-clear) | boolean | — |
disabled | Disabled | boolean | — |
expand-trigger | Sub-level expansion trigger: click (default) / hover (120ms delay to prevent misfires) | string | click |
field-names | Field-alias JSON ({ label, value, children, disabled }) matching the tree-select contract; lazy-load results are mapped alike | string | — |
filterable | Searchable (flat path results) | boolean | — |
item-height | Fixed virtual row height in px (with virtual, default 36) | string | 36 |
label | Accessible name of the trigger (aria-label), taking priority over value/placeholder | string | — |
loading | Loading state: trigger spinner + aria-busy; the panel shows a loading placeholder | boolean | — |
max-tag-count | Collapse multi-select tags beyond the count into +N (with title listing hidden items) | number | — |
multiple | Multi-select (cascading checkboxes; fully-checked children roll the parent into the value) | boolean | — |
open | Controlled open state (single source of truth); in the mobile form (touch / viewport <768px) the open panel is hosted by an oas-bottom-sheet bottom sheet (swipe-down/backdrop/Esc to close); desktop keeps the floating dropdown | boolean | — |
options | Cascade options, JSON array, supports children / disabled | CascaderOption[] | string | [] |
placeholder | Placeholder text | — | — |
placement | Panel placement in 12 directions (invalid falls back to bottom; auto-flip kept) | string | bottom |
separator | Path separator (default /) | string | / |
show-all-levels | Show the full path (default true); false shows only the leaf | string | true |
size | Size preset small / medium (default) / large | string | medium |
status | Validation status: error / warning / success; error mirrors aria-invalid | string | — |
value | Path array (JSON), e.g. ["zj","hz"] | string[] | string[][] | null | [] |
value-mode | Multi-select value strategy: all (default, full paths) / parentFirst / onlyLeaf | string | all |
virtual | Per-column virtual scrolling (columns above 50 items render via oas-virtual-list; short columns/search/loading keep normal rendering) | boolean | — |
Property (JS property; see each row for attribute reflection)
| Property | Description | Type | Default |
|---|---|---|---|
value | Current value: the selected path array, or an array of selected paths when multiple | string[] | string[][] | null | — |
Events
| Event | Description |
|---|---|
oas-blur | Dispatched on trigger blur; detail.value shares the oas-change shape (inner focus moves do not false-fire) |
oas-change | Selection change, detail: { value } (path array) |
oas-clear | Fires on clear-button click; detail is the pre-clear value |
oas-focus | Dispatched on trigger focus; detail.value shares the oas-change shape (inner focus moves do not false-fire) |
oas-open-change | Open state flips, detail: { open } |
oas-search | Fires on filterable input, detail: { value } |
Slots
| Name | Description |
|---|---|
template[slot="option"] | Custom option row template; [data-option-label] nodes bind the option text |
template[slot="suffix-icon"] | Custom trailing trigger icon (replaces the default arrow; yields to the spinner while loading) |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-cascader-dropdown-height | 240px |
--oas-glass-blur | none |
--oas-glass-ring | transparent |