JGuang

Cascader

Cascader · Mantine Core

Preview
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Cascader

Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Version and compatibility

Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.

Use case

Choose a value from hierarchical options.

Native props used by this example

These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.

Component propExplanation
Cascader.labelVisible label or component-specific label content.
Cascader.dataLocal options/nodes; keep unique values and native schema.

Interaction and state checks

Current scenarios: Default. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.

Business integration boundary

Demo content/state is local, without server persistence, authorization or fetching. A static display component is not a completed business workflow.

Host setup

Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.

Runnable usage

import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';


function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const [value, setValue] = useState(35);
return <M.Cascader label="Location" data={[{ value: "asia", label: "Asia", children: [{ value: "china", label: "China" }] }]} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

Preview states

Default

The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.

Usage

Cascader lets users select a value from hierarchical data: picking an option in one column reveals its children in the next column. The value is a path from the root option to the selected one, for example ['asia', 'jp', 'tokyo'].

All demos on this page switch to the flat list layout on small screens, open them on a larger screen to see the columns.

Data prop

Data passed to the data prop is an array of CascaderOption objects:

  • Each option must have a unique value across the whole data tree
  • label is optional, value is used when it is not set
  • children contains an array of nested options
  • disabled prevents an option from being selected or expanded
import { CascaderOption } from '@mantine/core';

const data: CascaderOption[] = [
  {
    value: 'asia',
    label: 'Asia',
    children: [
      {
        value: 'jp',
        label: 'Japan',
        children: [{ value: 'tokyo', label: 'Tokyo' }],
      },
    ],
  },
];

Value and onChange

Cascader value is an array of option values or null when nothing is selected. onChange is called with the selected path and the corresponding CascaderOption objects:

import { useState } from 'react';
import { Cascader, CascaderOption } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string[] | null>(['europe', 'fr', 'paris']);

  const handleChange = (path: string[] | null, options: CascaderOption[]) => {
    setValue(path);
    console.log(path, options);
  };

  return <Cascader data={data} value={value} onChange={handleChange} />;
}

By default, only leaf options (options without children) can be selected. Clicking a parent option only expands the next column.

Change on select

Set the changeOnSelect prop to allow selecting any option, not only leaves. Clicking a parent option then both selects it and expands the next column:

Close on select

closeOnSelect controls whether the dropdown is closed after a value is selected. By default, the dropdown stays open, unless allowDeselect is set to false:

Allow deselect

By default, clicking the selected option again removes the value. Set allowDeselect={false} to disable this behavior:

Expand trigger

By default, the next column is opened when an option is clicked. Set expandTrigger="hover" to open the next column on hover instead – selecting a leaf still requires a click:

With expandTrigger="hover", the open column stays in place while the cursor moves diagonally toward it. Set safeAreaPolygon={false} to disable this behavior, or pass an object to configure Floating UI safePolygon options:

Flat list layout

Columns require horizontal space and do not fit narrow screens. Set withColumns={false} to render options as a flat list of full paths instead:

Combine withColumns with the use-matches hook to render columns on desktop and the flat list on mobile:

import { Cascader, useMatches } from '@mantine/core';

function Demo() {
  const withColumns = useMatches({ base: false, sm: true });
  return <Cascader withColumns={withColumns} data={data} />;
}

Max displayed levels

Use the maxDisplayedLevels prop (3 by default) to limit the number of columns displayed next to each other. When the limit is exceeded, the first columns are hidden and a control to reveal them is displayed on the left:

Searchable

Set the searchable prop to allow filtering options. In search mode, the columns are replaced with a flat list of full paths that match the query:

Use the filter prop to customize how paths are matched and renderSearchOption to customize how search results are rendered:

import { Cascader, CascaderOption } from '@mantine/core';

function Demo() {
  return (
    <Cascader
      searchable
      data={data}
      filter={(query, options: CascaderOption[]) =>
        options.some((option) => String(option.label).toLowerCase().includes(query.toLowerCase()))
      }
      renderSearchOption={(query, options) => options.map((option) => option.label).join(' → ')}
    />
  );
}

Controlled search value

Use searchValue and onSearchChange props to control the search value:

import { useState } from 'react';
import { Cascader } from '@mantine/core';

function Demo() {
  const [searchValue, setSearchValue] = useState('');
  return (
    <Cascader
      searchable
      data={data}
      searchValue={searchValue}
      onSearchChange={setSearchValue}
    />
  );
}

Nothing found

Set the nothingFoundMessage prop to display a message when no options match the search query:

Display value

By default, the selected path is displayed in the input as option labels joined by the separator (/ by default). Use the separator prop to change the divider and the formatValue prop to fully customize the input label:

Column width

Use the columnWidth prop to set a fixed width for every column and maxDropdownHeight (260 by default) to set the max height of a column:

Custom option rendering

Use the renderOption prop to customize how options are rendered in columns. The function receives the option and its column level:

Check icon

Use the withCheckIcon prop to toggle the check icon on the selected option and checkIconPosition to change its position (left or right):

Disabled options

Set the disabled property on an option to prevent it from being selected or expanded. Disabled options are skipped during keyboard navigation:

Left and right sections

Cascader supports leftSection and rightSection props the same way as other inputs:

Clearable

Set the clearable prop to display the clear button when a value is selected:

Use the clearSectionMode prop to control how the clear button and rightSection are rendered:

Scrollable columns

Each column is scrollable. Use scrollAreaProps to pass props down to the ScrollArea of each column:

Control dropdown opened state

Use dropdownOpened, defaultDropdownOpened, onDropdownOpen and onDropdownClose props to control the dropdown opened state:

import { useState } from 'react';
import { Cascader } from '@mantine/core';

function Demo() {
  const [dropdownOpened, setDropdownOpened] = useState(false);
  return (
    <Cascader
      data={data}
      dropdownOpened={dropdownOpened}
      onDropdownOpen={() => setDropdownOpened(true)}
      onDropdownClose={() => setDropdownOpened(false)}
    />
  );
}

Use comboboxProps to pass props down to the underlying Combobox and Popover. For example, set position to change the dropdown position:

In flat list mode (withColumns={false}) the dropdown width can be controlled with comboboxProps={{ width }}. In columns mode the dropdown is sized to the columns:

Inside Popover

To use Cascader inside a Popover, set comboboxProps={{ withinPortal: false }}:

Styles API

Use the Styles API to customize Cascader styles:

Input props

Read only

Set readOnly to make the input read only. When readOnly is set, Cascader will not open the dropdown and will not call the onChange function.

Disabled

Set disabled to disable the input. When disabled is set, the user cannot interact with the input and Cascader will not open the dropdown.

Error state

Success state

Loading state

Set the loading prop to display a loader in the right section instead of the chevron:

Accessibility

Cascader columns are rendered as nested listboxes with option elements. To make the component accessible, set the label prop or pass aria-label to the component:

import { Cascader } from '@mantine/core';

// Set aria-label when the input does not have a visible label
function Demo() {
  return <Cascader data={data} aria-label="Pick location" />;
}

Keyboard interactions

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

Cascader(_props)
export interface CascaderOption {
  /** Option value, must be unique across the whole data tree */
  value: string;

  /** Option label, if not set `value` is used instead */
  label?: React.ReactNode;

  /** Nested options */
  children?: CascaderOption[];

  /** If set, the option cannot be selected or expanded */
  disabled?: boolean;
}

export interface CascaderFormatValueInput {
  /** Selected path from root to node */
  value: string[];

  /** Option chain resolved from the selected path */
  options: CascaderOption[];
}

export type CascaderFormatValue = (input: CascaderFormatValueInput) => React.ReactNode;

export type CascaderSafeAreaPolygonOptions = Omit<SafePolygonOptions, 'blockPointerEvents'>;

export type CascaderStylesNames =
  | __InputStylesNames
  | ComboboxLikeStylesNames
  | 'columnsList'
  | 'columnsOverflow'
  | 'column'
  | 'columnScroll'
  | 'columnOption'
  | 'columnOptionLabel'
  | 'columnOptionIcon'
  | 'columnOptionCheck'
  | 'columnEmpty'
  | 'flatOption';

export interface CascaderProps
  extends
    BoxProps,
    __BaseInputProps,
    StylesApiProps<CascaderFactory>,
    ElementProps<'input', 'size' | 'value' | 'defaultValue' | 'onChange'> {
  /** Hierarchical options data */
  data: CascaderOption[];

  /** Controlled selected path from root to node */
  value?: string[] | null;

  /** Uncontrolled selected path from root to node */
  defaultValue?: string[] | null;

  /** Called when the selected path changes with the path and the resolved option chain */
  onChange?: (value: string[] | null, options: CascaderOption[]) => void;

  /** If set, any intermediate option can be selected, not only leaf options @default false */
  changeOnSelect?: boolean;

  /** Determines whether the dropdown should be closed when a value is selected, defaults to `!allowDeselect` */
  closeOnSelect?: boolean;

  /** If set, the selected value can be deselected by selecting it again @default true */
  allowDeselect?: boolean;

  /** If set, the check icon is displayed on the selected option @default true */
  withCheckIcon?: boolean;

  /** Position of the check icon relative to the option label @default 'right' */
  checkIconPosition?: 'left' | 'right';

  /** Renders the dropdown as cascading columns. When `false`, options are rendered as a flat list of paths, the same way as search results (useful for narrow/mobile layouts) @default true */
  withColumns?: boolean;

  /** Determines how the next column is opened @default 'click' */
  expandTrigger?: 'click' | 'hover';

  /** Determines whether the next column stays open while the cursor moves toward it, applicable only when `expandTrigger="hover"`. Pass an object to configure safe polygon behavior. @default true */
  safeAreaPolygon?: boolean | CascaderSafeAreaPolygonOptions;

  /** If set, options can be searched by their flattened paths @default false */
  searchable?: boolean;

  /** Controlled search value */
  searchValue?: string;

  /** Uncontrolled search value */
  defaultSearchValue?: string;

  /** Called when the search value changes */
  onSearchChange?: (value: string) => void;

  /** Custom search filter, matched against the full option path */
  filter?: (query: string, options: CascaderOption[]) => boolean;

  /** Custom rendering of a search result row */
  renderSearchOption?: (query: string, options: CascaderOption[]) => React.ReactNode;

  /** A function to format the selected path displayed in the input, should return a string */
  formatValue?: CascaderFormatValue;

  /** Custom rendering of an option in columns */
  renderOption?: (option: CascaderOption, level: number) => React.ReactNode;

  /** Path separator displayed in the input and search results @default '/' */
  separator?: React.ReactNode;

  /** Width of each column */
  columnWidth?: number | string;

  /** Maximum number of columns (levels) displayed next to each other, deeper levels replace earlier ones @default 3 */
  maxDisplayedLevels?: number;

  /** `aria-label` and `title` of the control that reveals levels hidden before the visible ones by `maxDisplayedLevels` @default 'Show previous levels' */
  previousLevelsControlLabel?: string;

  /** `aria-label` and `title` of the control that reveals levels hidden after the visible ones by `maxDisplayedLevels` @default 'Show next levels' */
  nextLevelsControlLabel?: string;

  /** Max height of a column before it becomes scrollable @default 260 */
  maxDropdownHeight?: number | string;

  /** Message displayed when there are no options or search results */
  nothingFoundMessage?: React.ReactNode;

  /** If set, the clear button is displayed when a value is selected @default false */
  clearable?: boolean;

  /** Determines how the clear button and `rightSection` are rendered @default 'both' */
  clearSectionMode?: ClearSectionMode;

  /** Props passed down to the clear button */
  clearButtonProps?: InputClearButtonProps;

  /** Called when the clear button is clicked */
  onClear?: () => void;

  /** Controlled dropdown opened state */
  dropdownOpened?: boolean;

  /** Uncontrolled dropdown opened state */
  defaultDropdownOpened?: boolean;

  /** Called when the dropdown opens */
  onDropdownOpen?: () => void;

  /** Called when the dropdown closes */
  onDropdownClose?: () => void;

  /** Props passed down to the underlying `Combobox` component */
  comboboxProps?: Record<string, any>;

  /** Props passed down to the dropdown `ScrollArea` */
  scrollAreaProps?: ScrollAreaProps;

  /** Controls the default chevron color */
  chevronColor?: MantineColor;

  /** Props passed down to the hidden input */
  hiddenInputProps?: Omit<React.ComponentProps<'input'>, 'value'>;

  /** Opens the dropdown when the input is focused in `searchable` mode @default true */
  openOnFocus?: boolean;
}

export type CascaderFactory = Factory<{
  props: CascaderProps;
  ref: HTMLInputElement;
  stylesNames: CascaderStylesNames;
  variant: InputVariant;
}>;

export type Mod = Record<string, any> | string;

export type BoxMod = Mod | Mod[] | BoxMod[];

export interface BoxProps extends MantineStyleProps {
  /** Class added to the root element, if applicable */
  className?: string | undefined;

  /** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
  style?: MantineStyleProp;

  /** CSS variables defined on root component element */
  __vars?: CssVarsProp;

  /** `size` property passed down the HTML element */
  __size?: string;

  /** Breakpoint above which the component is hidden with `display: none` */
  hiddenFrom?: MantineBreakpoint;

  /** Breakpoint below which the component is hidden with `display: none` */
  visibleFrom?: MantineBreakpoint;

  /** Determines whether component should be hidden in light color scheme with `display: none` */
  lightHidden?: boolean;

  /** Determines whether component should be hidden in dark color scheme with `display: none` */
  darkHidden?: boolean;

  /** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
  mod?: BoxMod;
}

export type ElementProps<
  ElementType extends React.ElementType,
  PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;

export interface CSSProperties extends React.CSSProperties {
  [key: string]: any;
}

type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);

export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;

export type CssVariable = `--${string}`;

export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;

export type CssVars<Variable extends string = CssVariable> =
  | CssVariables<Variable>
  | ((theme: MantineTheme) => CssVariables<Variable>)
  | CssVars<Variable>[];

export type CssVarsProp<Variable extends string = CssVariable> =
  | CssVars<Variable>
  | CssVars<Variable>[];

type WrapperProps = React.ComponentProps<'div'> & DataAttributes;

export interface __BaseInputProps extends __InputWrapperProps, __InputProps {
  /** Props passed down to the root element */
  wrapperProps?: WrapperProps;
}

export type __InputStylesNames = InputStylesNames | InputWrapperStylesNames;

export type InputStylesNames = 'input' | 'wrapper' | 'section' | 'bottomSection';

export type InputVariant = 'default' | 'filled' | 'unstyled';

export interface __InputProps {
  /** Content section displayed on the left side of the input */
  leftSection?: React.ReactNode;

  /** Left section width, used to set `width` of the section and input `padding-left`, by default equals to the input height */
  leftSectionWidth?: React.CSSProperties['width'];

  /** Props passed down to the `leftSection` element */
  leftSectionProps?: React.ComponentProps<'div'>;

  /** Sets `pointer-events` styles on the `leftSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
  leftSectionPointerEvents?: React.CSSProperties['pointerEvents'];

  /** Content section displayed on the right side of the input */
  rightSection?: React.ReactNode;

  /** Right section width, used to set `width` of the section and input `padding-right`, by default equals to the input height */
  rightSectionWidth?: React.CSSProperties['width'];

  /** Props passed down to the `rightSection` element */
  rightSectionProps?: React.ComponentProps<'div'>;

  /** Sets `pointer-events` styles on the `rightSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
  rightSectionPointerEvents?: React.CSSProperties['pointerEvents'];

  /** Sets `required` attribute on the `input` element */
  required?: boolean;

  /** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem @default theme.defaultRadius */
  radius?: MantineRadius;

  /** Sets `disabled` attribute on the `input` element */
  disabled?: boolean;

  /** Controls input `height`, horizontal `padding`, and `font-size` @default 'sm' */
  size?: MantineSize | (string & {});

  /** Determines whether the input should have `cursor: pointer` style. Use when input acts as a button-like trigger (e.g., `component="button"` for Select/DatePicker). @default false */
  pointer?: boolean;

  /** Determines whether the input should have red border and red text color when the `error` prop is set @default true */
  withErrorStyles?: boolean;

  /** Determines whether the input should have green border when the `success` prop is set @default true */
  withSuccessStyles?: boolean;

  /** HTML `size` attribute for the input element (number of visible characters) */
  inputSize?: string;

  /** Section to be displayed when the input is `__clearable` and `rightSection` is not defined */
  __clearSection?: React.ReactNode;

  /** Determines whether the `__clearSection` should be displayed if it is passed to the component, has no effect if `rightSection` is defined */
  __clearable?: boolean;

  /** Determines how the clear button and rightSection are rendered @default 'both' */
  __clearSectionMode?: ClearSectionMode;

  /** Right section displayed when both `__clearSection` and `rightSection` are not defined */
  __defaultRightSection?: React.ReactNode;

  /** Displays loading indicator in the left or right section @default false */
  loading?: boolean;

  /** Position of the loading indicator @default 'right' */
  loadingPosition?: 'left' | 'right';

  /** Content section displayed at the bottom of the input, inside the border */
  __bottomSection?: React.ReactNode;

  /** Props passed down to the `__bottomSection` element */
  __bottomSectionProps?: React.ComponentProps<'div'>;
}

export type InputWrapperStylesNames =
  | 'root'
  | InputLabelStylesNames
  | InputDescriptionStylesNames
  | InputErrorStylesNames
  | InputSuccessStylesNames;

export interface __InputWrapperProps {
  /** Contents of `Input.Label` component. If not set, label is not displayed. */
  label?: React.ReactNode;

  /** Contents of `Input.Description` component. If not set, description is not displayed. */
  description?: React.ReactNode;

  /** Contents of `Input.Error` component. If not set, error is not displayed. */
  error?: React.ReactNode;

  /** Contents of `Input.Success` component. If not set, success is not displayed. */
  success?: React.ReactNode;

  /** Adds required attribute to the input and a red asterisk on the right side of label @default false */
  required?: boolean;

  /** If set, the required asterisk is displayed next to the label. Overrides `required` prop. Does not add required attribute to the input. @default false */
  withAsterisk?: boolean;

  /** Props passed down to the `Input.Label` component */
  labelProps?: InputLabelProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Description` component */
  descriptionProps?: InputDescriptionProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Error` component */
  errorProps?: InputErrorProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Success` component */
  successProps?: InputSuccessProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Render function to wrap the input element. Useful for adding tooltips, popovers, or other wrappers around the input. @default React.Fragment */
  inputContainer?: (children: React.ReactNode) => React.ReactNode;

  /** Controls order and visibility of wrapper elements. Only elements included in this array will be rendered. @default ['label', 'description', 'input', 'error'] */
  inputWrapperOrder?: ('label' | 'input' | 'description' | 'error')[];
}

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

Factory<{
  props: CascaderProps;
  ref: HTMLInputElement;
  stylesNames: CascaderStylesNames;
  variant: InputVariant;
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
React.ReactNode
InputLabelProps
DataAttributes
PlaceholderPolymorphicProps
InputDescriptionProps
InputErrorProps
InputSuccessProps
React.ComponentProps<'div'>
MantineRadius
MantineSize
ClearSectionMode
StylesApiProps<CascaderFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
Omit<SafePolygonOptions, 'blockPointerEvents'>
SafePolygonOptions
InputClearButtonProps
ScrollAreaProps
MantineColor
Omit<React.ComponentProps<'input'>, 'value'>
React.ComponentProps<'input'>
HTMLInputElement
InputLabelStylesNames
InputDescriptionStylesNames
InputErrorStylesNames
InputSuccessStylesNames
ComboboxLikeStylesNames