Skip to main content

Combo Box

Combo Box lets users choose one or more items from an options menu.

Use a Combo Box to offer a list of choices where multiple selection is possible.

Example

Live Editor
function Default() {
  ComboBoxRenderer = () => {
    const openSnackbar = useContext(SnackbarContext);
    const options = [
      { value: 'melon', label: 'Melon' },
      { value: 'honeydew-melon', label: 'Honeydew melon' },
      { value: 'watermelon', label: 'Watermelon' },
      { value: 'apple', label: 'Apple' },
      { value: 'mango', label: 'Mango' }
    ];

    const handleOnChange = (e) => {
      openSnackbar({
        title: `onChange happened. Value selected is ${e.value}`,
        kind: 'positive'
      });
    };

    return (
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        onChange={handleOnChange}
      />
    );
  };
  return (
    <SnackbarProvider offsetTop={76}>
      <ComboBoxRenderer />
    </SnackbarProvider>
  );
}
Result
Loading...

Usage Rules

When to use: Combo Box vs. List of Checkboxes vs. Select

The Combo Box component has feature parity with the Select component, and yet behaves slightly differently and offers many additional, but optional features. In general when both components could be used, make a decision based on the following criteria:

  • If the options needs to be cleared after being selected, use the Combo Box component. Do not "hack" the Select component to clear the options by adding a "None Selected" option to the option list.
  • Avoid mixing these two components in a composition (eg. filter toolbar), as that could lead to user confusion.

When it comes to the simple use case of selecting one or more options from a list (without additional features like creating option or searching within that list), follow this decision tree:

Select

  • Users can select only one value from a list of mutually exclusive options.
  • There are 5–15 options.

Checkbox List

  • There’s a small number of options (5 or fewer) from which users can choose.
  • There are 5–15 options.
  • Seeing the selected item is more important than viewing the other available options.
  • Space is not limited, or the list could be wrapped by a scroll container.

Radio Group

  • Users can select only one value from a list of mutually exclusive options.
  • There’s a small number of options (5 or fewer) from which users can choose.

Combo Box

  • There are 5–15 options.
  • There are more than 15 options.
  • Users can select one or many from the list of options.
  • Seeing an overview of all items, selected or not, aids in task completion.
  • There’s not a clear or optimal default option.
  • The list of options needs to be searchable.
  • The user needs to be able to add new options.

Variants

Adaptive Width

If you would like to have the width of ComboBox adaptive to text, use the boolean prop adaptiveWidth

Live Editor
function adaptiveWidthBox() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      adaptiveWidth
    />
  );
}
Result
Loading...

Adaptive Width with min-width for Options

If you are using adaptiveWidth prop and would like to give the options a minimum width, you can use optionsMinWidth

info

When optionsMinWidth is set to string value such as max-content additional padding width is applied. A horizontal scrollbar might be seen when a numeric value is set with type string with virtualized list.

Live Editor
function optionWithMinWidth() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <>
      <p>Flexible width max-content</p>
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        adaptiveWidth
        optionsMinWidth={'max-content'}
      />
      <p>Fixed min-width</p>
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        adaptiveWidth
        optionsMinWidth={300}
      />
    </>
  );
}
Result
Loading...

Async

Use the async kind to load options from a remote data source as the user types.

Live Editor
function Async() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const filterOptions = (inputValue) =>
    options.filter((option) =>
      option.label.toLowerCase().includes(inputValue.toLowerCase())
    );

  const promiseOptions = (inputValue) =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(filterOptions(inputValue));
      }, 1000);
    });

  return (
    <ComboBox
      multiple
      kind="async"
      loadOptions={promiseOptions}
      placeholder="Select some fruit"
      aria-label="async-example"
    />
  );
}
Result
Loading...

Async Creatable

The async-creatable kind supports loading options from a remote data source and creating new options.

Live Editor
function AsyncCreatable() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const filterOptions = (inputValue) =>
    options.filter((option) =>
      option.label.toLowerCase().includes(inputValue.toLowerCase())
    );

  const promiseOptions = (inputValue) =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(filterOptions(inputValue));
      }, 1000);
    });

  const onChange = (newValue, actionMeta) => {
    const { action, option } = actionMeta;

    if (action === 'create-option') {
      console.group('Option Created');
      console.log(option);
      console.groupEnd();
    }
  };

  return (
    <ComboBox
      multiple
      kind="async-creatable"
      loadOptions={promiseOptions}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="async-creatable-example"
    />
  );
}
Result
Loading...

BlockScroll

You can block/unblock the scrolling when the ComboBox is open by using blockScroll, default false.

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <>
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        blockScroll={true}
      />
    </>
  );
}
Result
Loading...

Clearable

Use isClearable boolean prop to clear the selected option(s), you can also use onClear callback event when value is cleared.

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      isClearable={true}
      onClear={() => console.log('onClear')}
    />
  );
}
Result
Loading...

Creatable

The creatable kind let users add new options along with the existing ones.

Live Editor
function Creatable() {
  const defaultOptions = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const [value, setValue] = useState();
  const [options, setOptions] = useState(defaultOptions);

  const onCreateOption = (inputValue) => {
    const newOption = {
      value: `${inputValue}-${options.length}`,
      label: inputValue
    };

    setOptions([...options, newOption]);
    setValue(newOption);

    console.group('Value Created');
    console.log(newOption);
    console.log('Running other code...');
    console.groupEnd();
  };

  const onChange = (newValue) => {
    setValue(newValue);
  };

  return (
    <ComboBox
      kind="creatable"
      value={value}
      options={options}
      onChange={onChange}
      onCreateOption={onCreateOption}
      placeholder="Select some fruit"
      aria-label="creatable-example"
    />
  );
}
Result
Loading...

Custom Labelled Status Indicating Input

We can use indicatorCustomLabels props which needs to match the StatusIndicatorLabelTypes exported from StatusIndicator component to show custom labels, when no custom labels are passed, default props are used.

Live Editor
function comboBoxWithCustomLabelledStatusIndicator() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const customLabels = {
    progress: 'Updating...',
    positive: 'Updated!',
    negative: 'Failed!'
  };

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      indicatorType="positive"
      indicatorWithLabel
      indicatorCustomLabels={customLabels}
    />
  );
}
Result
Loading...

Custom dataTestId

You can now test the component using custom testid using dataTestId component.

Live Editor
function DataTestID() {
  const options = [
    { value: 'melon', label: 'Melon', description: 'I am a sample text' },
    {
      value: 'apple',
      label: 'Apple',
      description: 'Apple a day keeps doctor away'
    },
    { value: 'mango', label: 'Mango', description: 'The sweatest fruit' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      dataTestId="custom-test-id"
    />
  );
}
Result
Loading...

Custom filter

You can customize the filter to search the options by adding the prop customFilterOption to the component.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon juice' },
    { value: 'apple', label: 'Apple cake' },
    { value: 'mango', label: 'Mango smoothie' }
  ];

  const customFilterOption = (option, rawInput) => {
    const words = rawInput.toLowerCase().split(' ');
    return (
      option.data.__isNew__ ||
      words.reduce(
        (acc, cur) => acc && option.label.toLowerCase().endsWith(cur),
        true
      )
    );
  };

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      customFilterOption={customFilterOption}
    />
  );
}
Result
Loading...

Description

You can add description to the option using description prop for items.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon', description: 'I am a sample text' },
    {
      value: 'apple',
      label: 'Apple',
      description: 'Apple a day keeps doctor away'
    },
    { value: 'mango', label: 'Mango', description: 'The sweatest fruit' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
    />
  );
}
Result
Loading...

Disabled

Live Editor
function Disabled() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const selectedOption = options[0];

  return (
    <ComboBox
      disabled
      value={selectedOption}
      options={options}
      placeholder="Select some fruit"
      aria-label="disabled-example"
    />
  );
}
Result
Loading...

Disabled Option

You can disable an option by setting the isDisabled property to true.

Live Editor
function WithDisabledOption() {
  const options = [
    { value: 'melon', label: 'Melon', isDisabled: true },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      placeholder="Select some fruit"
      aria-label="disabled-option-example"
    />
  );
}
Result
Loading...

FormatOptionLabel

You can customize the options by using formatOptionLabel and returning ReactNode

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon', numberOfApps: 10 },
    { value: 'honeydew-melon', label: 'Honeydew melon', numberOfApps: 5 },
    { value: 'watermelon', label: 'Watermelon', numberOfApps: 8 },
    { value: 'fig', label: 'Fig', numberOfApps: 5 },
    { value: 'apple', label: 'Apple', numberOfApps: 3 },
    { value: 'mango', label: 'Mango', numberOfApps: 34 }
  ];

  const formatOptionLabel = ({ value, label, numberOfApps }) => (
    <div style={{ display: 'flex' }}>
      <div>{label}</div>
      <div
        style={{ position: 'absolute', right: 30 }}
      >{`${numberOfApps} Apps`}</div>
    </div>
  );

  return (
    <>
      <ComboBox
        options={options}
        label="Fruits"
        formatOptionLabel={formatOptionLabel}
        placeholder="Select some fruit"
      />
    </>
  );
}
Result
Loading...

Free-text input with autocomplete

Use kind="input" property to allow users type any kind of text even when it's not listed in the options. Such behavior is useful when you need a search-like input with autocomplete capabilities.

info

ComboBox component with kind="input" does not support multiple and noOptionsMessage properties.

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const [value, setValue] = useState();

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();

    setValue(newValue || undefined);
  };

  return (
    <ComboBox
      kind="input"
      label="Fruits"
      placeholder="Select some fruit"
      options={options}
      onChange={onChange}
      value={value}
    />
  );
}
Result
Loading...

Group Label Normalization

We have a boolean prop groupLabelNormalize to Normalize the label casing of the group labels.

Live Editor
function Multi() {
  const options = [
    {
      label: 'fruits',
      options: [
        { value: 'melon', label: 'Melon' },
        { value: 'mango', label: 'Mango' }
      ]
    },
    {
      label: 'other Fruits',
      options: [
        { value: 'noneydew-melon', label: 'Honeydew melon' },
        { value: 'watermelon', label: 'Watermelon' },
        { value: 'apple', label: 'Apple' }
      ]
    }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <ComboBox
      multiple
      options={options}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="disabled-example"
      label="Fruits"
      optionalText="Optional"
      groupLabelNormalize
    />
  );
}
Result
Loading...

Icons & App Icons

The options supports some kind of icons:

  • You can add DuoTone Icons by adding prop iconName.
  • You can add Platform Icons by adding prop platform that accepts a array of iconNames
Live Editor
function Default() {
  ComboBoxRenderer = () => {
    const openSnackbar = useContext(SnackbarContext);
    const options = [
      {
        value: 'leardroid',
        label: 'Learn Droid', 
        iconName: 'DuoBarChart',
        platform: [
          'PlatformAndroidFilled',
          'PlatformVizioTVFilled',
          'PlatformIosFilled',
          'PlatformWindowsFilled'
        ]
      },
      {
        value: 'cryptoharvest',
        label: 'Crypto Harvest',
        iconName: 'DuoBarChart',
        platform: [
          'PlatformAndroidFilled',
          'PlatformVizioTVFilled'
        ],
        isDisabled: true
      },
      {
        value: 'piechart',
        label: 'DuoTone PieChart',
        iconName: 'DuoPieChart'
      },
      {
        value: 'amazingmarket',
        label: 'Amazing Market',
        iconName: 'DuoStackedBarChart',
        platform: [
          "PlatformWindowsFilled"
        ]
      }
    ];

    const handleOnChange = (e) => {
      openSnackbar({
        title: `onChange happened. Value selected is ${e.value}`,
        kind: 'positive'
      });
    };

    return (
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select ..."
        onChange={handleOnChange}
      />
    );
  };
  return (
    <SnackbarProvider offsetTop={76}>
      <div style={{width: '250px'}}>
        <ComboBoxRenderer />
      </div>
    </SnackbarProvider>
  );
}
Result
Loading...
  • You can also add App Icons by adding the prop appIcon (ReactNode) to option data. It's an external component (mfe-components) and recommended size is small.
import { AppIcon } from '@adjust/mfe-components ';

function Default() {
const options = [
{
value: 'cryptoharvest',
label: 'Crypto Harvest',
appIcon: <AppIcon appId="com.adjust-demo.cryptoharvest" appName="Finance - Crypto Harvest" size="small" />,
platform: [
'PlatformAndroidFilled',
'PlatformVizioTVFilled'
],
isDisabled: true
},
{
value: 'pocketbanking',
label: 'Pocket Banking',
appIcon: <AppIcon appId="com.adjust-demo.pocketbanking" appName="Finance - Pocket Banking" size="small" />,
platform: [
'PlatformAndroidFilled',
]
},
{
value: 'amazingmarket',
label: 'Amazing Market',
appIcon: <AppIcon appId="com.adjust-demo.amazingmarket" appName="Shopping - Amazing Market" size="small" />,
platform: [
"PlatformWindowsFilled"
]
}
];

return (
<ComboBox
options={options}
label="Fruits"
placeholder="Select ..."
/>
);
}

InfoText

A text value to show information related to the selection.

You can change the position of the info text to be on top of the ComboBox.

warning

Please avoid using the Info Text on top of the ComboBox when you also need the optional label.

warning
Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <>
      <ComboBox
        multiple
        options={options}
        label="Fruits"
        infoText="Info text"
        placeholder="Select some fruit"
      />
      <ComboBox
        multiple
        options={options}
        label="Fruits"
        infoText="Info text"
        infoTextTop
        placeholder="Select some fruit"
      />
    </>
  );
}
Result
Loading...

We also support text tags inside the Info Text. Like strong, b, i and a.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const infoText = `Info <b>text</b> really <i>long</i> that <a href='#'> goes </a> on to a <strong> second </strong>line`;

  return (
    <ComboBox
      multiple
      options={options}
      label="Fruits"
      infoText={infoText}
      placeholder="Select some fruit"
    />
  );
}
Result
Loading...

Invalid State

A user can set a invalid state when the selection is improper.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const infoText = `This is a sample invalid infoText`;

  return (
    <ComboBox
      multiple
      options={options}
      label="Fruits"
      infoText={infoText}
      invalid
      placeholder="Select some fruit"
    />
  );
}
Result
Loading...

Loading State

You can add a spinner to ComboBox by adding isLoading prop

Live Editor
function ComboBoxWithSpinner() {
  const options = [
    { value: 'melon', label: 'Melon', numberOfApps: 10 },
    { value: 'honeydew-melon', label: 'Honeydew melon', numberOfApps: 5 },
    { value: 'watermelon', label: 'Watermelon', numberOfApps: 8 },
    { value: 'fig', label: 'Fig', numberOfApps: 5 },
    { value: 'apple', label: 'Apple', numberOfApps: 3 },
    { value: 'mango', label: 'Mango', numberOfApps: 34 }
  ];

  return (
    <>
      <ComboBox
        options={options}
        label="Fruits"
        isLoading
        placeholder="Select some fruit"
      />
    </>
  );
}
Result
Loading...

Loading More Data

The component supports virtual infinite loading by adding following props:

  • onLoadMoreData a callback function to load more data.
  • hasMoreData
  • isLoadingMoreData
  • loadingMoreDataLabel custom label of status indicator.

To integrate virtual infinite loading with React Query please refer to useInfiniteQuery hook and this example.

note

Virtual infinite loading is enabled for more than 30 items.

Live Editor
function MyComboBox() {
  const generateMoreData = useCallback(
    (offset = 0, chunkSize = 35) =>
      Array.from({ length: chunkSize }, (_, index) => {
        const id = offset + index + 1;

        return {
          label: `Label ${id}`,
          value: `value_${id}`,
        };
      }),
    []
  );

  const maxNumberOrRows = 140;
  const initialData = useMemo(() => generateMoreData(), [generateMoreData]);
  const [options, setOptions] = useState(initialData);
  const [isLoadingMoreData, setIsLoadingMoreData] = useState(false);

  const onLoadMoreData = useCallback(() => {
    setIsLoadingMoreData(true);
    setTimeout(() => {
      const generatedData = generateMoreData(options.length);
      const newData = [...options, ...generatedData];
      setOptions((preOptions) => [...preOptions, ...generatedData]);
      setIsLoadingMoreData(false);
    }, 3000);
  }, [options.length, generateMoreData]);

  return (
    <>
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        hasMoreData={options.length < maxNumberOrRows}
        isLoadingMoreData={isLoadingMoreData}
        onLoadMoreData={onLoadMoreData}
        multiple
      />
    </>
  );
}
Result
Loading...

Label and Info Text

Use an info text to give specific instructions to help users correctly fill in the data. You can also use it to clarify how the data will be used.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      multiple
      options={options}
      label="Fruits"
      infoText="Want some melons?"
      placeholder="Select some fruit"
    />
  );
}
Result
Loading...

In cases when ComboBox is used in constrained spaces, it is possible to specify maxMenuHeight for the options box.

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
  ];

  const maxMenuHeight = 30 * 3;

  return (
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        maxMenuHeight={maxMenuHeight}
      />
  );
}
Result
Loading...

You can manage the placement of the dropdown by using menuPlacement.

Has 3 variants: auto, bottom and top and which is default to auto.

Live Editor
function customizeOptions() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'honeydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'fig', label: 'Fig' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <>
      <ComboBox
        options={options}
        label="Fruits"
        placeholder="Select some fruit"
        menuPlacement="top"
      />
    </>
  );
}
Result
Loading...

Multi Creatable

The multi-creatable kind supports creating new multiple values.

Live Editor
function MultiCreatable() {
  const [value, setValue] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const handleChange = (value) => setValue(value);
  const handleInput = (inputValue) => setInputValue(inputValue);

  const handleKeyDown = (event) => {
    if (!inputValue) return;
    switch (event.key) {
      case 'Enter':
      case 'Tab':
        setValue([
          ...value,
          {
            value: `${inputValue}-${value.length}`,
            label: inputValue
          }
        ]);
        setInputValue('');
        event.preventDefault();
    }
  };

  return (
    <ComboBox
      kind="multi-creatable"
      value={value}
      inputValue={inputValue}
      onChange={handleChange}
      onInputChange={handleInput}
      onKeyDown={handleKeyDown}
      placeholder="Type anything and press `Enter`"
      aria-label="creatable-example"
    />
  );
}
Result
Loading...

Multi Selection

You can provide multiple prop to enable multiple selection.

You also can do the range selection with Shift key, by selecting an option first then press and hold the Shift to select the range.

Live Editor
function Multi() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' },
    { value: 'melon1', label: 'Melon1' },
    { value: 'noneydew-melon1', label: 'Honeydew melon1' },
    { value: 'watermelon1', label: 'Watermelon1' },
    { value: 'apple1', label: 'Apple1' },
    { value: 'mango1', label: 'Mango1' },
    { value: 'melon2', label: 'Melon2' },
    { value: 'noneydew-melon2', label: 'Honeydew melon2' },
    { value: 'watermelon2', label: 'Watermelon2' },
    { value: 'apple2', label: 'Apple2' },
    { value: 'mango2', label: 'Mango2' },
    { value: 'melon3', label: 'Melon3' },
    { value: 'noneydew-melon3', label: 'Honeydew melon3' },
    { value: 'watermelon3', label: 'Watermelon3' },
    { value: 'apple3', label: 'Apple3' },
    { value: 'mango3', label: 'Mango3' }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <ComboBox
      multiple
      options={options}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="multi-example"
    />
  );
}
Result
Loading...

Multi Selection with Select All Option

You can provide select all options by using selectAll prop with custom label. It's an optional prop to allow display Select All Option.

Live Editor
function Multi() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const selectAll = {
    selectAllText: 'Select All',
    unselectAllText: 'Unselect All'
  };

  return (
    <ComboBox
      multiple
      options={options}
      placeholder="Select some fruit"
      aria-label="multi-example"
      selectAll={selectAll}
    />
  );
}
Result
Loading...

Multi Selection with limiter banner

You can provide multiLimit prop to enable multiple selection with the limited selected options which can be displayed. The rest of selected options will be displayed as limiter banner.

Live Editor
function MultiLimit() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <ComboBox
      multiple
      options={options}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="multi-example"
      multiLimit={2}
    />
  );
}
Result
Loading...

You can also provide multiLimit prop as 'zero' to show only limiter banner.

Where first limiter banner will be displayed as value of selected.

Live Editor
function MultiLimit() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <ComboBox
      multiple
      options={options}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="multi-example"
      multiLimit="zero"
    />
  );
}
Result
Loading...

Open Programmatically

You can use the isOpenDefault prop to load the component open and the isOpen prop to open the component programmatically. Please, remember in this case it will stay open until you change isOpen to false.

note

Opening the ComboBox with either of these props set to true will not block scroll on the page.

Live Editor
function Open() {
  // set the component open prop
  const [isOpen, setIsOpen] = useState(undefined);

  const [posts, setPosts] = useState([]);
  const fetchPost = async () => {
    const response = await fetch(
      'https://hub.dummyapis.com/employee?noofRecords=100&idStarts=1001'
    );
    const data = await response.json();
    setPosts(data);
  };

  useEffect(() => {
    fetchPost();
  }, []);
  const options = posts.map(({ id, age }) => ({
    value: age,
    label: id
  }));

  const onInputChange = (newValue, actionMeta) => {
    // disable the isOpen prop onInputChange
    isOpen && setIsOpen(undefined);

    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <>
      <Button
        onClick={() => setIsOpen(isOpen ? undefined : true)}
        label="toggle ComboBox"
      />
      <ComboBox
        options={options}
        onInputChange={onInputChange}
        placeholder="Select some fruit"
        aria-label="open-example"
        isOpen={isOpen}
      />
    </>
  );
}
Result
Loading...

Optional Text

You can show the field as optional or custom text by using the optionalText prop as string.

Live Editor
function Multi() {
  const options = [
    {
      label: 'fruits',
      options: [
        { value: 'melon', label: 'Melon' },
        { value: 'mango', label: 'Mango' }
      ]
    },
    {
      label: 'other Fruits',
      options: [
        { value: 'noneydew-melon', label: 'Honeydew melon' },
        { value: 'watermelon', label: 'Watermelon' },
        { value: 'apple', label: 'Apple' }
      ]
    }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <ComboBox
      multiple
      options={options}
      onChange={onChange}
      placeholder="Select some fruit"
      aria-label="disabled-example"
      label="Fruits"
      optionalText="Optional"
    />
  );
}
Result
Loading...

You can add extra piece of information to the label by including a tooltip.

Live Editor
function LabelAndInfoText() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      multiple
      options={options}
      label="Fruits"
      infoText="Want some melons?"
      placeholder="Select some fruit"
      labelIconTooltip={{
        content: 'hello',
        position: 'top'
      }}
    />
  );
}
Result
Loading...

Readonly

ComboBox component supports readOnly property which can be used to prevent password managers from auto-filling internal's input element.

Live Editor
function Default() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  // By default, input element should be writable
  const [readOnly, setReadOnly] = useState(true);

  // Making input element writable when user clicks on the ComboBox
  const onFocus = useCallback(() => setReadOnly(false), []);

  // Making input element readonly when ComboBox loses focus
  const onBlur = useCallback(() => setReadOnly(true), []);

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      readOnly={readOnly}
      onFocus={onFocus}
      onBlur={onBlur}
    />
  );
}
Result
Loading...

Readonly options

You can use readonly property in ComboBox options to specify options that cannot be deleted or removed

Live Editor
function MultiWithReadonlyOptions() {
  const options = [
    { value: 'melon', label: 'Melon', readonly: true },
    { value: 'mango', label: 'Mango', readonly: true },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' }
  ];

  const [value, setValue] = useState(options.slice(0, 2));

  const handleChange = (value) => setValue(value);

  return (
    <ComboBox
      multiple
      value={value}
      options={options}
      onChange={handleChange}
      placeholder="Select some fruit"
      aria-label="multi-readonly-options-example"
    />
  );
}
Result
Loading...

Status Indicating

Live Editor
function comboBoxStatusIndicator() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      indicatorType="positive"
    />
  );
}
Result
Loading...

ComboBox component's Status Indicator withLabel, here you can see defaultLabels for indicatorType.

defaultLabels: { progress: 'Saving…', positive: 'Saved!', negative: 'Disconnected!' }

Live Editor
function comboBoxStatusIndicatorWithLabel() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      indicatorType="positive"
      indicatorWithLabel
    />
  );
}
Result
Loading...

Searchable ComboBox

Set the prop isSearchable as false to disable this feature on the ComboBox, as this option is enabled by default.

Live Editor
function comboBoxStatusIndicator() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      isSearchable={false}
    />
  );
}
Result
Loading...

Tabs Options

This allows to use Tabs inside ComboBox

Live Editor
function optionWithTabs() {
  const tabItems = [
    { id: 'relevant', label: 'Relevant' },
    { id: 'all', label: 'All' }
  ];

  const allOptions = {
    all: [
      { value: 'melon', label: 'Melon' },
      { value: 'honeydew-melon', label: 'Honeydew melon' },
      { value: 'watermelon', label: 'Watermelon' }
    ],
    relevant: [
      { value: 'fig', label: 'Fig' },
      { value: 'apple', label: 'Apple' },
      { value: 'mango', label: 'Mango' }
    ]
  };

  const selectAll = {
    selectAllText: 'Select All',
    unselectAllText: 'Unselect All'
  };

  return (
    <ComboBox
      options={allOptions}
      label="Fruits"
      placeholder="Select some fruit"
      tabItems={tabItems}
      initialSelectedTab="all"
      multiple
      selectAll={selectAll}
    />
  );
}
Result
Loading...

Tooltip on ComboBox options

To add Tooltip to an option you can add tooltipText to your options object. There are 3 props you can alter on the comboBox tooltip: delay: true, position: 'right' and offset: [0, 10]. To alter these values you can add the prop optionsTooltipProps which takes an object, ie: optionsTooltipProps={{position: 'left'}}.

Use the formatSelectedOption on the ComboBox to customize the content of the tooltip shown when you hover on the selected option.

formatSelectedOption is passed the selected option and should return a ReactNode.

Live Editor
function optionTooltip() {
  const options = [
    { value: 'melon', label: 'Melon', tooltipText: 'Melon is a fruit' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const tooltipOverride = {
    position: 'left',
    delay: false,
    offset: [0, 14]
  };

  const formatSelectedOption = ({ value, label, tooltipText }) => {
    return (
      <div>
        <p>Selected option value is: {value}</p>
        <p>Selected option label is: {label}</p>
        {tooltipText ? <p>{tooltipText}</p> : null}
      </div>
    );
  };

  return (
    <ComboBox
      formatSelectedOption={formatSelectedOption}
      options={options}
      optionsTooltipProps={tooltipOverride}
      label="Fruits"
      placeholder="Select some fruit"
      isSearchable={false}
    />
  );
}
Result
Loading...

Truncate tokens

The prop truncateTokens is a boolean to truncate the multi-value-chips in ComboBox multi-selection.

Live Editor
function Multi() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'noneydew-melon', label: 'Honeydew melon' },
    { value: 'watermelon', label: 'Watermelon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const onChange = (newValue, actionMeta) => {
    console.group('Value Changed');
    console.log(newValue);
    console.log(`action: ${actionMeta.action}`);
    console.groupEnd();
  };

  return (
    <div style={{ width: 300 }}>
      With truncate
      <ComboBox
        multiple
        options={options}
        onChange={onChange}
        placeholder="Select some fruit"
        aria-label="multi-example"
        truncateTokens
        multiLimit={2}
      />
      Without truncate
      <ComboBox
        multiple
        options={options}
        onChange={onChange}
        placeholder="Select some fruit"
        aria-label="multi-example"
        multiLimit={2}
      />
    </div>
  );
}
Result
Loading...

Virtualization

This component supports virtualization for the list of options. This improves rendering performance when handling large amounts of data.

note

We have tested that this component works with up to 100.000 items, with a minor delay when rendering the larger numbers. If you require a longer list of options, please consider using a different approach.

Live Editor
function Default() {
  const options = new Array(1000).fill().map((_, index) => ({
    value: `value_${index}`,
    label: `Label ${index}`
  }));

  return (
    <ComboBox
      options={options}
      label="Fruits"
      placeholder="Select some fruit"
      multiple
    />
  );
}
Result
Loading...

Examples

Multi-creatable

Example for onBlur prop on multi-creatable kind of ComboBox

Live Editor
function onBlurExample() {
  const options = [
    { value: 'melon', label: 'Melon' },
    { value: 'apple', label: 'Apple' },
    { value: 'mango', label: 'Mango' }
  ];

  const [value, setValue] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const handleChange = (value) => setValue(value);
  const handleInput = (inputValue) => setInputValue(inputValue);

  const handleKeyDown = (event) => {
    if (!inputValue) return;
    switch (event.key) {
      case 'Enter':
      case 'Tab':
        setValue([
          ...value,
          {
            value: `${inputValue}-${value.length}`,
            label: inputValue
          }
        ]);
        setInputValue('');
        event.preventDefault();
    }
  };

  const handleBlur = () => {
    if (inputValue.length) {
      const newArray = [
        ...value,
        {
          value: inputValue,
          label: inputValue
        }
      ];
      setValue(newArray);
      setInputValue('');
      event.preventDefault();
    }
  };

  return (
    <ComboBox
      kind="multi-creatable"
      value={value}
      inputValue={inputValue}
      onChange={handleChange}
      onBlur={handleBlur}
      onInputChange={setInputValue}
      onKeyDown={handleKeyDown}
    />
  );
}
Result
Loading...

Multi-creatable with Multi-selection

Live Editor
function MyTable() {
  const columns = useMemo(
    () => [
      {
        Header: 'First Name',
        accessor: 'firstName',
        Footer: () => 'Total'
      },
      {
        Header: 'Last Name',
        accessor: 'lastName',
        width: 300,
        Cell: () => {
          const [value, setValue] = useState([]);
          const [options, setOptions] = useState([]);
          const [inputValue, setInputValue] = useState('');

          const handleChange = (value) => setValue(value);
          const handleInput = (inputValue) => setInputValue(inputValue);

          const onCreateOption = (inputValue) => {
            const newOption = {
              value: `${inputValue}-${value.length}`,
              label: inputValue
            };
            setValue([...value, newOption]);
            setOptions([...options, newOption]);
            setInputValue('');
            event.preventDefault();
          };

          const handleKeyDown = (event) => {
            if (!inputValue) return;
            switch (event.key) {
              case 'Enter':
              case 'Tab':
                onCreateOption(inputValue);
            }
          };

          const selectAll = {
            selectAllText: 'Select All',
            unselectAllText: 'Unselect All'
          };

          return (
            <ComboBox
              kind="multi-creatable"
              value={value}
              inputValue={inputValue}
              options={options}
              onCreateOption={onCreateOption}
              onChange={handleChange}
              onInputChange={handleInput}
              onKeyDown={handleKeyDown}
              placeholder="Type anything and press `Enter`"
              aria-label="creatable-example"
              multiLimit={1}
              selectAll={selectAll}
            />
          );
        }
      }
    ],
    []
  );

  const data = [
    {
      firstName: 'Just a name',
      lastName: 'Some lastName',
      points: 10
    },
    {
      firstName: 'Another name',
      lastName: 'Another lastName',
      points: 11
    },
    {
      firstName: 'Random name',
      lastName: 'Random lastName',
      points: 700
    }
  ];

  return (
    <div style={{ height: 300 }}>
      <TableV2 data={data} columns={columns} />
    </div>
  );
}
Result
Loading...

Props

ComboBox

NameTypeDefault
kind
The kind of the combo box
"input" | "creatable" | "async" | "async-creatable" | "multi-creatable"
noOptionsMessage
Function that returns text to display when there are no options
((data: { inputValue: string; }) => string | null)
multiple
Use this prop to allow multiple selection
boolean
label
The label of the combo box
string
value
The value of the select. Reflected by the selected option.
Option | OptionType[] | null
indicatorType
The prop for indicatorType which is required to enable the indicator and shows the status based on its given type without label.
"negative" | "positive" | "warning" | "progress"
indicatorWithLabel
This prop enables the display of a label next the status indicator. If no custom label is passed, it uses the default labels.
boolean
indicatorCustomLabels
The prop for status indicator with custom labels with StatusIndicatorLabelTypes, if not passed uses default props that can be exported from StatusIndicator component.
StatusIndicatorLabelTypes
options
Array of options, Array of group options, and Object of tab options, that populate the select menu. The option has two required properties {label: string, value: string}. It can be extended with any additional properties.
OptionType[] | GroupBase<OptionType>[] | Record<string, OptionType[] | GroupBase<OptionType>[]>
labelIconTooltip
The labelIconTooltip content can either be pure text or any rich content wrapped in a React component The labelIconTooltip content will be shown when hovering the label icon. The labelIconTooltip position is used to define the positioning of the tooltip When the labelIconTooltip content is not provided, the label icon will be not shown.
Omit<TooltipProps, "children">
loadOptions
Function that returns a promise, which is the set of options to be used once the promise resolves
((inputValue: string) => Promise<OptionType[]>)
onClear
onClear callback event which triggers when combobox is cleared
(() => void)
onChange
Handle change events when selecting an option. The first argument is a new value. The second argument is an object containing info about the change event. For example, name of the action (select-option) and the selected option.
((value: OptionType | OnChangeValue<OptionType, MultiType>, action?: ActionMeta<OptionType>) => void) | ((value: OptionType, action?: ActionMeta<...>) => void) | undefined
inputValue
Current input value
string
onInputChange
Handle change events on the input. The first argument is a new value. The second argument is an object containing info about the change event. For example, name of the action (select-option) and the selected option.
((newValue: string, actionMeta: InputActionMeta) => void)
onKeyDown
Handle the keyboard enter event. The argument is a keyboard event.
KeyboardEventHandler<HTMLDivElement>
onBlur
Handle blur events on the control. The argument is the event.
FocusEventHandler
onFocus
Handle focus events on the control. The argument is the event.
FocusEventHandler
onCreateOption
If provided, this will be called with the input value when a new option is created, and onChange will not be called. Use this when you need more control over what happens when new options are created.
((inputValue: string) => void)
loadingMessage
Function that returns text to display when loading options
((data: { inputValue: string; }) => string | null)
formatCreateOptionLabel
Function that returns text to display in the create option
((inputValue: string) => string)
isLoading
Boolean prop to show spinner in ComboBox
boolean
infoText
Instructions to help users correctly fill in the data
string
infoTextTop
positions the infoText on top of the ComboBox
boolean
invalid
Visually notify the user that the provided value is invalid
boolean
disabled
Use this prop if the component should not be interactive
boolean
id
Native HTML attribute: Link . Internally, this component uses a random id. We use it to associate an input with a label element. As the random id might break snapshot tests it can be overridden by setting an explicit id.
string
name
Native HTML attribute: Link
string
placeholder
This text is shown when no item is selected
string
aria-label
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
aria-labelledby
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
isOpen
Controls if the comboBox is open or closed
boolean
isOpenDefault
Loads the comboBox open
boolean
isSearchable
enables search functionality
boolean
optionsTooltipProps
enables tooltip message over options
OptionsTooltipProps
multiLimit
Limit for MultiSelect
number | "zero"
adaptiveWidth
Boolean value for ComboBox adaptive width
boolean
optionsMinWidth
String or number value for ComboBox options min width
string | number
formatOptionLabel
Formats option labels in the menu and control as React components
((option: OptionType, labelMeta: FormatOptionLabelMeta<OptionType>) => ReactNode)
formatSelectedOption
Customizes tooltip content shown when you hover on the selected option
((option: OptionType) => ReactNode)
tabItems
Array of tab items. See the structure of TabItemType
TabItemType[]
initialSelectedTab
initial tabId of selected tab item
string
autoFocus
Focus the control when it is mounted: Link
boolean
menuPlacement
Placement prop for dropdown
"auto" | "top" | "bottom"
blockScroll
The prop allows controlling the placement of the dropdown.
boolean
optionalText
optionalText value to show the field is optional with custom text
string
readOnly
Native HTML attribute: Link
boolean
isClearable
Boolean prop to show the ClearIndicator
boolean
selectAll
The prop allows to display Select All option with custom label.
SelectAllType
dataTestId
Data-testid for the component
string
truncateTokens
Boolean to truncate the multi value chips
boolean
customFilterOption
Function to customize search
((option: FilterOptionOption<Option>, inputValue: string) => boolean) | null
groupLabelNormalize
Boolean to set Grouplabel lowercase
boolean
maxMenuHeight
Defines max-height for the menu list
number
onLoadMoreData
This prop is used for Infinite Loading, more than 30 options
((startIndex?: number, stopIndex?: number) => void | Promise<void>) | undefined
isLoadingMoreData
This prop is used for Infinite Loading, more than 30 options
boolean
hasMoreData
This prop is used for Infinite Loading, more than 30 options
boolean
loadingMoreDataLabel
This prop is used for Infinite Loading, more than 30 options
string
css
Add custom styles to this component. Use with caution. Learn more here: Link
SupportedStyleAttributes
inputFieldCss
SupportedStyleAttributes
data-{foo}
Data attributes can be used by testing libraries to retrieve components or assert their existence
string
* - the prop is required.