TreeSelect

Select with hierarchical tree data

Usage

TreeSelect allows picking one or more values from hierarchical tree data. It supports three selection modes: single, multiple, and checkbox (with parent-child cascade).

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
    />
  );
}

Data prop

Data passed to the data prop must follow the same rules as the Tree component:

  • Data must be an array of TreeNodeData objects
  • Each node must have unique value and label keys
  • Each node can have a children key with an array of child nodes
import { TreeNodeData } from '@mantine/core';

const data: TreeNodeData[] = [
  {
    value: 'fruits',
    label: 'Fruits',
    children: [
      { value: 'apple', label: 'Apple' },
      { value: 'banana', label: 'Banana' },
    ],
  },
  { value: 'milk', label: 'Milk' },
];

Selection modes

TreeSelect supports three selection modes controlled by the mode prop:

  • single (default) – single value selection, renders as an input
  • multiple – multiple value selection, renders as pills
  • checkbox – checkbox selection with parent-child cascade, renders as pills

Multiple mode

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite items"
      placeholder="Pick values"
      data={data}
      mode="multiple"
      defaultExpandAll
    />
  );
}

Checkbox mode

In checkbox mode, checking a parent node automatically checks all its children. Unchecking a parent unchecks all children. If only some children are checked, the parent shows an indeterminate state.

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Select categories"
      placeholder="Pick values"
      data={data}
      mode="checkbox"
      defaultExpandAll
    />
  );
}

Controlled

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

// Single mode
function SingleDemo() {
  const [value, setValue] = useState<string | null>(null);
  return <TreeSelect data={[]} value={value} onChange={setValue} />;
}

// Multiple or checkbox mode
function MultipleDemo() {
  const [value, setValue] = useState<string[]>([]);
  return <TreeSelect data={[]} mode="multiple" value={value} onChange={setValue} />;
}

Searchable

Set the searchable prop to allow filtering options by user input. When searching, matching nodes and their ancestors are shown:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      searchable
    />
  );
}

Nothing found

Set the nothingFoundMessage prop to display a given message when no options match the search query or there is no data available:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      searchable
      nothingFoundMessage="Nothing found..."
    />
  );
}

Clearable

Set the clearable prop to display the clear button in the right section:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      defaultValue="iphone"
      defaultExpandedValues={['electronics', 'phones']}
      clearable
    />
  );
}

Expand on click

Set the expandOnClick prop to also toggle expansion when clicking a parent node (in addition to the chevron). Behavior depends on the selection mode:

  • single and multiple – clicking a parent only expands/collapses it. Only leaf nodes can be selected.
  • checkbox – clicking a parent both toggles its checked state and expands it.
import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      expandOnClick
    />
  );
}

Connecting lines

TreeSelect renders connecting lines between parent and child nodes by default. Set withLines={false} to disable them:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Without connecting lines"
      placeholder="Pick value"
      data={data}
      defaultExpandAll
      withLines={false}
    />
  );
}

Check strictly

Set checkStrictly to disable parent-child cascade in checkbox mode. Each node's checked state becomes fully independent:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Select items"
      placeholder="Pick values"
      data={data}
      mode="checkbox"
      checkStrictly
      defaultExpandAll
    />
  );
}

Checked strategy

The checkedStrategy prop controls which checked nodes appear in the value and pills in checkbox mode:

  • child (default) – only leaf nodes appear in the value
  • all – all checked nodes (parents and children) appear in the value
  • parent – only the topmost fully-checked parents appear in the value
import { useState } from 'react';
import { Stack, TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  const [childValue, setChildValue] = useState<string[]>([]);
  const [allValue, setAllValue] = useState<string[]>([]);
  const [parentValue, setParentValue] = useState<string[]>([]);

  return (
    <Stack>
      <TreeSelect
        label="checkedStrategy: child (default)"
        placeholder="Pick values"
        data={data}
        mode="checkbox"
        checkedStrategy="child"
        defaultExpandAll
        value={childValue}
        onChange={setChildValue}
      />
      <TreeSelect
        label="checkedStrategy: all"
        placeholder="Pick values"
        data={data}
        mode="checkbox"
        checkedStrategy="all"
        defaultExpandAll
        value={allValue}
        onChange={setAllValue}
      />
      <TreeSelect
        label="checkedStrategy: parent"
        placeholder="Pick values"
        data={data}
        mode="checkbox"
        checkedStrategy="parent"
        defaultExpandAll
        value={parentValue}
        onChange={setParentValue}
      />
    </Stack>
  );
}

Max values

Set the maxValues prop to limit the number of selected values in multiple and checkbox modes:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Pick up to 3 items"
      placeholder="Pick values"
      data={data}
      mode="multiple"
      maxValues={3}
      defaultExpandAll
    />
  );
}

renderNode

The renderNode callback allows you to customize node rendering in the dropdown. It is called with an object containing node, level, expanded, hasChildren, selected, checked, and indeterminate properties:

import { FileTextIcon, FolderOpenIcon, FolderSimpleIcon } from '@phosphor-icons/react';
import { Group, Text, TreeSelect, TreeSelectProps } from '@mantine/core';
import { data } from './data';

const renderTreeNode: TreeSelectProps['renderNode'] = ({ node, hasChildren, expanded }) => (
  <Group gap="xs">
    {hasChildren ? (
      expanded ? (
        <FolderOpenIcon color="var(--mantine-color-yellow-9)" size={16} />
      ) : (
        <FolderSimpleIcon color="var(--mantine-color-yellow-9)" size={16} />
      )
    ) : (
      <FileTextIcon size={16} />
    )}
    <Text size="sm">{node.label}</Text>
  </Group>
);

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      renderNode={renderTreeNode}
      defaultExpandAll
    />
  );
}

Scrollable dropdown

By default, the options list is wrapped with ScrollArea.Autosize. You can control the dropdown max-height with the maxDropdownHeight prop:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      defaultExpandAll
      maxDropdownHeight={200}
    />
  );
}

Combobox props

You can override Combobox props with comboboxProps. This is useful when you need to change some of the props that are not exposed by TreeSelect, for example withinPortal:

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

function Demo() {
  return <TreeSelect comboboxProps={{ withinPortal: false }} data={[]} />;
}

Change dropdown z-index

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

function Demo() {
  return <TreeSelect comboboxProps={{ zIndex: 1000 }} data={[]} />;
}

Control dropdown opened state

You can control the dropdown opened state with the dropdownOpened prop. Additionally, you can use onDropdownClose and onDropdownOpen to listen to dropdown opened state changes.

import { TreeSelect, Button } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { data } from './data';

function Demo() {
  const [dropdownOpened, { toggle }] = useDisclosure();
  return (
    <>
      <Button onClick={toggle} mb="md">
        Toggle dropdown
      </Button>

      <TreeSelect
        label="Your favorite item"
        placeholder="Pick value"
        data={data}
        dropdownOpened={dropdownOpened}
      />
    </>
  );
}

Dropdown position

By default, the dropdown is displayed below the input if there is enough space; otherwise it is displayed above the input. You can change this behavior by setting the position and middlewares props, which are passed down to the underlying Popover component.

Example of a dropdown that is always displayed above the input:

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      comboboxProps={{ position: 'top', middlewares: { flip: false, shift: false } }}
    />
  );
}

Dropdown width

To change the dropdown width, set the width prop in comboboxProps. By default, the dropdown width is equal to the input width.

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      comboboxProps={{ width: 200, position: 'bottom-start' }}
    />
  );
}

Dropdown offset

To change the dropdown offset, set the offset prop in comboboxProps:

import { TreeSelect } from '@mantine/core';
import { data } from './data';
import classes from './Demo.module.css';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      classNames={classes}
      comboboxProps={{ position: 'bottom', middlewares: { flip: false, shift: false }, offset: 0 }}
    />
  );
}

Dropdown animation

By default, dropdown animations are disabled. To enable them, you can set transitionProps, which will be passed down to the underlying Transition component.

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      comboboxProps={{ transitionProps: { transition: 'pop', duration: 200 } }}
    />
  );
}

Dropdown padding

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <>
      <TreeSelect
        label="Zero padding"
        placeholder="Pick value"
        data={data}
        comboboxProps={{ dropdownPadding: 0 }}
      />
      <TreeSelect
        mt="md"
        label="10px padding"
        placeholder="Pick value"
        data={data}
        comboboxProps={{ dropdownPadding: 10 }}
      />
    </>
  );
}

Dropdown shadow

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      comboboxProps={{ shadow: 'md' }}
    />
  );
}

Keyboard navigation

TreeSelect supports the following keyboard interactions when the dropdown is open:

  • ArrowRight – expand the highlighted parent node
  • ArrowLeft – collapse the highlighted parent node, or move to its parent
  • ArrowUp / ArrowDown – move between options
  • Enter – select the highlighted option

Expand state

You can control the expanded state of nodes:

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

// Expand specific nodes by default
<TreeSelect data={data} defaultExpandedValues={['fruits', 'vegetables']} />

// Expand all nodes by default
<TreeSelect data={data} defaultExpandAll />

// Controlled expanded state
<TreeSelect
  data={data}
  expandedValues={expandedValues}
  onExpandedChange={setExpandedValues}
/>

Left and right sections

TreeSelect supports leftSection and rightSection props. These sections are rendered with absolute positioning inside the input wrapper. You can use them to display icons, input controls, or any other elements.

You can use the following props to control sections styles and content:

  • rightSection / leftSection – React node to render on the corresponding side of input
  • rightSectionWidth/leftSectionWidth – controls the width of the right section and padding on the corresponding side of the input. By default, it is controlled by the component size prop.
  • rightSectionPointerEvents/leftSectionPointerEvents – controls the pointer-events property of the section. If you want to render a non-interactive element, set it to none to pass clicks through to the input.
import { TreeSelect } from '@mantine/core';
import { SquaresFourIcon } from '@phosphor-icons/react';
import { data } from './data';

function Demo() {
  const icon = <SquaresFourIcon size={16} />;
  return (
    <>
      <TreeSelect
        data={data}
        leftSectionPointerEvents="none"
        leftSection={icon}
        label="Your favorite item"
        placeholder="Your favorite item"
      />
      <TreeSelect
        mt="md"
        data={data}
        rightSectionPointerEvents="none"
        rightSection={icon}
        label="Your favorite item"
        placeholder="Your favorite item"
      />
    </>
  );
}

Input props

TreeSelect component supports Input and Input.Wrapper component features and all input element props. The TreeSelect documentation does not include all features supported by the component – see the Input documentation to learn about all available features.

Input description

Variant
Size
Radius
import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Input label"
      description="Input description"
      placeholder="Pick value"
      data={data}
    />
  );
}

Read only

Set readOnly to make the input read only. When readOnly is set, TreeSelect will not show suggestions and will not call the onChange function.

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      defaultValue="iphone"
      readOnly
    />
  );
}

Disabled

Set disabled to disable the input. When disabled is set, the user cannot interact with the input and TreeSelect will not show suggestions.

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <TreeSelect
      label="Your favorite item"
      placeholder="Pick value"
      data={data}
      disabled
    />
  );
}

Error state

Invalid value

import { TreeSelect } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <>
      <TreeSelect
        label="Boolean error"
        placeholder="Boolean error"
        error
        data={data}
      />
      <TreeSelect
        mt="md"
        label="With error message"
        placeholder="With error message"
        error="Invalid value"
        data={data}
      />
    </>
  );
}

Get element ref

import { useRef } from 'react';
import { TreeSelect } from '@mantine/core';

function Demo() {
  const ref = useRef<HTMLInputElement>(null);
  return <TreeSelect ref={ref} />;
}

Accessibility

If TreeSelect is used without the label prop, it will not be announced properly by screen readers:

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

// Inaccessible input – screen reader will not announce it properly
function Demo() {
  return <TreeSelect />;
}

Set aria-label to make the input accessible. In this case the label will not be visible, but screen readers will announce it:

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

// Accessible input – it has aria-label
function Demo() {
  return <TreeSelect aria-label="My input" />;
}

If the label prop is set, the input will be accessible and it is not required to set aria-label:

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

// Accessible input – it has associated label element
function Demo() {
  return <TreeSelect label="My input" />;
}

To set aria-label on the clear button, use clearButtonProps. Note that this is required only when clearable is set.

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

function Demo() {
  return (
    <TreeSelect
      data={[]}
      clearable
      clearButtonProps={{
        'aria-label': 'Clear input',
      }}
    />
  );
}