A text input with a dropdown list of options.

Importing

The component can be imported via:

import { Combobox } from '@customerio/pluma-components/react';

Usage

A complete example of Combobox usage can be seen below, with the following arguments:

  • inputValue - the string text value to show inside the Combobox
  • selectedValue - they key of the currently selected dropdown option
  • onValueChange - a callback for when the inputValue changes
  • onSelectionChange - a callback for when an option is selected in the dropdown
  • options - an array of options, with the same shape as in PlumaSelect
import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		setFieldState({
			inputValue: value != null ? simpleOptions.find((o) => o.value === value)?.label ?? '' : '',
			selectedValue: value,
		});
	}}
	options={simpleOptions}
	placeholder="Choose an option"
 />
}

Controlling state

The Combobox accepts separate arguments for the text value shown in the input (inputValue), and for the dropdown option that is currently selected (selectedValue). It also accepts corresponding change callbacks: onValueChange and onSelectionChange respectively.

The component can be used with either inputValue or selectedValue arguments, or with both at the same time, depending on use case.

Fully controlled

This is the recommended setup, and allows developers to fully control the input and dropdown state, by passing in both inputValue and selectedValue, and their appropriate change handlers.

Note that an update to one of these values does not automatically update the other. This means that typing in the input will only invoke the onValueChange handler, while selecting an option in the dropdown will only call onSelectionChange. The developer must make sure to update the other property when one of them changes.

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : birdOptions.find((o) => o.label === value)?.value ?? null,
		});
	}}
	onSelectionChange={(value) => {
		setFieldState({
			inputValue: value != null ? birdOptions.find((o) => o.value === value)?.label ?? '' : '',
			selectedValue: value,
		});
	}}
	options={birdOptions}
	placeholder="Choose an option"
 />
}

selectedValue-only control

When only selectedValue is controlled, the component will set its internal input value to the selected option's label whenever an option is selected.

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [selectedValue, setSelectedValue] = useState(null);

 return <Combobox
	label="Choose an option"
	selectedValue={selectedValue}
	onSelectionChange={(value) => {
		setSelectedValue(value);
	}}
	options={birdOptions}
	placeholder="Choose an option"
 />
}

inputValue-only control

When only inputValue is controlled, the component will internally track the state of the selected dropdown option, and update it whenever a selection has been made ( either through pressing the Enter key or by clicking on an option).

On blur, the component will reset the selected value to an option with a matching label text, or null if no matching option is found.

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [inputValue, setInputValue] = useState('');

 return <Combobox
	label="Choose an option"
	inputValue={inputValue}
	onValueChange={(value) => {
		setInputValue(value);
	}}
	options={birdOptions}
	placeholder="Choose an option"
 />
}

Disabling options

Individual options can be disabled:

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});
	const options = [
		{ value: 'option_1', label: 'Option 1' },
		{ value: 'option_2', label: 'Option 2', isDisabled: true },
		{ value: 'option_3', label: 'Option 3' },
	];

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		setFieldState({
			inputValue: value != null ? options.find((o) => o.value === value)?.label ?? '' : '',
			selectedValue: value,
		});
	}}
	options={options}
	placeholder="Choose an option"
 />
}

Grouped options

You can pass in groups of options to the Combobox to create breaks between sets of options:

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});
	const options = [
		{
			label: 'Birds',
			options: birdOptions,
		},
		{
			label: 'Animals',
			options: [
				{
					value: 'lion',
					label: 'Lion',
				},
				{
					value: 'tiger',
					label: 'Tiger',
				},
				{
					value: 'bear',
					label: 'Bear'
				}
			]
		}
	];

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		let maybeOption = null;
		let stack = [...options].reverse();
		while (value != null && maybeOption == null && stack.length > 0) {
			let item = stack.pop();

			if (item.options != null) {
				stack.push(...item.options);
			} else {
				if (item.value === value) {
					maybeOption = item;
				}
			}
		}

		setFieldState({
			inputValue: maybeOption?.label ?? '',
			selectedValue: value,
		});
	}}
	options={options}
	placeholder="Choose an option"
 />
}

An entire group can be disabled by setting its option entry to isDisabled:

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});
	const options = [
		{
			label: 'Birds',
			options: birdOptions,
			isDisabled: true,
		},
		{
			label: 'Animals',
			options: [
				{
					value: 'lion',
					label: 'Lion',
				},
				{
					value: 'tiger',
					label: 'Tiger',
				},
				{
					value: 'bear',
					label: 'Bear'
				}
			]
		}
	];

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		let maybeOption = null;
		let stack = [...options].reverse();
		while (value != null && maybeOption == null && stack.length > 0) {
			let item = stack.pop();

			if (item.options != null) {
				stack.push(...item.options);
			} else {
				if (item.value === value) {
					maybeOption = item;
				}
			}
		}

		setFieldState({
			inputValue: maybeOption?.label ?? '',
			selectedValue: value,
		});
	}}
	options={options}
	placeholder="Choose an option"
 />
}

Virtualized rendering

Just like the Select component, Combobox supports virtualized option rendering.

This option is disabled by default, and can be enabled via the withVirtualizer argument. Optionally, the virtualizerOptions argument accepts the same options as the library's Virtualizer class, allowing you to customize the behavior if necessary.

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});
	const options = [
		{
			label: 'Birds',
			options: birdOptions,
			isDisabled: true,
		},
		{
			label: 'Animals',
			options: [
				{
					value: 'lion',
					label: 'Lion',
				},
				{
					value: 'tiger',
					label: 'Tiger',
				},
				{
					value: 'bear',
					label: 'Bear'
				}
			]
		}
	];

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		let maybeOption = null;
		let stack = [...longOptions].reverse();
		while (value != null && maybeOption == null && stack.length > 0) {
			let item = stack.pop();

			if (item.options != null) {
				stack.push(...item.options);
			} else {
				if (item.value === value) {
					maybeOption = item;
				}
			}
		}

		setFieldState({
			inputValue: maybeOption?.label ?? '',
			selectedValue: value,
		});
	}}
	options={longOptions}
	placeholder="Choose an option"
	withVirtualizer={true}
 />
}

Custom option components

The Combobox component accepts an optionComponent prop, which can be used to customize how the options in the dropdown are rendered. The prop accepts a component, which will be called with the following props:

  • option - the option object from the options array
  • isSelected - whether this is the currently selected option (the selectedValue matches this option)
  • isActive - whether this is the currently active option (it's currently hovered or focused via keyboard)

For example, we might define a custom option component like this:

const COLORS = [
	'red',
	'raspberry',
	'clementine',
	'yellow',
	'green',
	'teal',
	'blue',
	'plum',
	'purple',
	'grey',
	'outline',
];

function getIndex(option) {
	return birdOptions.findIndex((o) => o.value === option.value);
}

function CustomOptionComponent({ option, isSelected }) {
	const originalIndex = getIndex(option);

	return (
		<InlineStack alignInline="space-between" alignBlock="center" gap="100">
			<Label 
				color={COLORS[originalIndex % COLORS.length]}
			>
				{option.label}
			</Label>
			{isSelected ? <SelectMenuOptionSelectedIcon /> : null}
		</InlineStack>
	);
}

Which could then be used in the Combobox component:

import { Combobox } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
	}}
	onSelectionChange={(value) => {
		setFieldState({
			inputValue: value != null ? birdOptions.find((o) => o.value === value)?.label ?? '' : '',
			selectedValue: value,
		});
	}}
	options={birdOptions}
	placeholder="Choose an option"
	optionComponent={CustomOptionComponent}
 />
}

Asynchronous loading

The Combobox component supports loading options data asynchronously. It will display a loading spinner inside the input when isLoading is true. When performing filtering through an API, also set shouldFilter to false, to prevent the Combobox from filtering the results itself.

import { Combobox } from '@customerio/pluma-components/react';
import { useState, useCallback, useRef } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});

	const [options, setOptions] = useState(longFlatOptions);

	const [isLoading, setIsLoading] = useState(false);
	const timeoutRef = useRef(null);

	const debouncedFilterOptions = useCallback((value) => {
		if (timeoutRef.current != null) {
			clearTimeout(timeoutRef.current);
			timeoutRef.current = null;
		}

		setIsLoading(true);

		timeoutRef.current = setTimeout(() => {
			const searchValue = value.toLocaleLowerCase();
			const filteredOptions = longFlatOptions.filter((o) => (o.searchLabel ?? o.label).toLocaleLowerCase().includes(searchValue));

			setOptions(filteredOptions);
			setIsLoading(false);
			timeoutRef.current = null;
		}, 500);
	}, [setIsLoading, setOptions]);

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
		debouncedFilterOptions(value);
	}}
	onSelectionChange={(value) => {
		let maybeOption = null;
		let stack = [...longFlatOptions].reverse();
		while (value != null && maybeOption == null && stack.length > 0) {
			let item = stack.pop();

			if (item.options != null) {
				stack.push(...item.options);
			} else {
				if (item.value === value) {
					maybeOption = item;
				}
			}
		}

		const inputValue = maybeOption?.label ?? '';
		setFieldState({
			inputValue,
			selectedValue: value,
		});
		debouncedFilterOptions(inputValue);
	}}
	options={options}
	placeholder="Choose an option"
	isLoading={isLoading}
	shouldFilter={false}
 />
}

The Combobox also supports infinite scrolling (for paginated APIs), and will call the onLoadMore callback when scrolling towards the end of the list. The other props expected for this use case are:

  • shouldLoadMore - a boolean indicating whether the onLoadMore callback should be called at all (e.g. when there is a next page of data). Set to false if there are no more pages/there is no more data to fetch
  • isLoadingMore - whether more data is currently being fetched. While the API call issued via the onLoadMore callback is running, this should be true, and will show a loading spinner at the bottom of the list

Note: It is up to you to merge the results of the paginated API calls into one options array, which you pass into the component. The component itself only issues callbacks when required, but you control the state.

The example below shows a Combobox that simulates an API with filtering and pagination.

import { Combobox } from '@customerio/pluma-components/react';
import { useState, useCallback } from 'react';

export default function Example() {
	const [fieldState, setFieldState] = useState({
		inputValue: '',
		selectedValue: null,
	});

	const paginationInterval = 10;
	const [filteredOptions, setFilteredOptions] = useState(longFlatOptions);
	const [visibleOptionIndex, setVisibleOptionIndex] = useState(paginationInterval);

	const [isLoading, setIsLoading] = useState(false);
	const timeoutRef = useRef(null);

	const debouncedFilterOptions = useCallback((value) => {
		if (timeoutRef.current != null) {
			clearTimeout(timeoutRef.current);
			timeoutRef.current = null;
		}

		setIsLoading(true);

		timeoutRef.current = setTimeout(() => {
			const searchValue = value.toLocaleLowerCase();
			const filteredOptions = longFlatOptions.filter((o) => (o.searchLabel ?? o.label).toLocaleLowerCase().includes(searchValue));

			setFilteredOptions(filteredOptions);
			setIsLoading(false);
			setVisibleOptionIndex(paginationInterval);
			setShouldLoadMore(filteredOptions.length > paginationInterval);
			timeoutRef.current = null;
		}, 500);
	}, [setIsLoading, setFilteredOptions, setVisibleOptionIndex]);

	const options = filteredOptions.slice(0, visibleOptionIndex);

	const [isLoadingMore, setIsLoadingMore] = useState(false);
	const [shouldLoadMore, setShouldLoadMore] = useState(true);

	const onLoadMore = useCallback(() => {
		if (shouldLoadMore) {
			setIsLoadingMore(true);

			setTimeout(() => {
				const remainingOptionCount = filteredOptions.length - visibleOptionIndex;
				const nextVisibleOptionIndex =
					remainingOptionCount >= paginationInterval
						? visibleOptionIndex + paginationInterval
						: visibleOptionIndex + remainingOptionCount;

				setIsLoadingMore(false);
				setVisibleOptionIndex(nextVisibleOptionIndex);

				if (remainingOptionCount <= paginationInterval) {
					setShouldLoadMore(false);
				}
			}, 1000);
		}
	}, [shouldLoadMore, setIsLoadingMore, filteredOptions.length, visibleOptionIndex]);

 return <Combobox
	label="Choose an option"
	inputValue={fieldState.inputValue}
	selectedValue={fieldState.selectedValue}
	onValueChange={(value) => {
		setFieldState({
			inputValue: value,
			selectedValue: value === '' ? null : fieldState.selectedValue,
		});
		debouncedFilterOptions(value);
	}}
	onSelectionChange={(value) => {
		let maybeOption = null;
		let stack = [...longFlatOptions].reverse();
		while (value != null && maybeOption == null && stack.length > 0) {
			let item = stack.pop();

			if (item.options != null) {
				stack.push(...item.options);
			} else {
				if (item.value === value) {
					maybeOption = item;
				}
			}
		}

		const inputValue = maybeOption?.label ?? '';
		setFieldState({
			inputValue,
			selectedValue: value,
		});
		debouncedFilterOptions(inputValue);
	}}
	options={options}
	placeholder="Choose an option"
	isLoading={isLoading}
	shouldFilter={false}
	onLoadMore={onLoadMore}
	shouldLoadMore={!isLoading && shouldLoadMore}
	isLoadingMore={isLoadingMore}
 />
}

Customizing labels

If a string label is not sufficient, a component can be used to render the label.

In React, the label prop accepts any ReactNode.

import { Combobox, Icon } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [fieldState, setFieldState] = useState({
    inputValue: '',
    selectedValue: null,
  });

  return <Combobox
    label={<>Custom label <Icon name="campaigns" isInline={true} size="sm" /></>}
    inputValue={fieldState.inputValue}
    selectedValue={fieldState.selectedValue}
    onValueChange={(value) => {
      setFieldState({
        inputValue: value,
        selectedValue: value === '' ? null : fieldState.selectedValue,
      });
    }}
    onSelectionChange={(value) => {
      setFieldState({
        inputValue: value != null ? simpleOptions.find((o) => o.value === value)?.label ?? '' : '',
        selectedValue: value,
      });
    }}
    options={simpleOptions}
  />
}

Customizing descriptions

If a string description is not sufficient, a component can be used to render the description.

In React, the description prop accepts any ReactNode.

import { Combobox, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [fieldState, setFieldState] = useState({
    inputValue: '',
    selectedValue: null,
  });

  return <Combobox
    ariaLabel="Custom description input"
    description={<>Custom description, <Link href="https://docs.customer.io" isExternal={true} variant="secondary">see docs for details</Link></>}
    inputValue={fieldState.inputValue}
    selectedValue={fieldState.selectedValue}
    onValueChange={(value) => {
      setFieldState({
        inputValue: value,
        selectedValue: value === '' ? null : fieldState.selectedValue,
      });
    }}
    onSelectionChange={(value) => {
      setFieldState({
        inputValue: value != null ? simpleOptions.find((o) => o.value === value)?.label ?? '' : '',
        selectedValue: value,
      });
    }}
    options={simpleOptions}
  />
}

Customizing errors

If a string error is not sufficient, a component can be used to render the error.

In React, the error prop accepts any ReactNode.

import { Combobox, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [fieldState, setFieldState] = useState({
    inputValue: '',
    selectedValue: null,
  });

  return <Combobox
    ariaLabel="Custom error input"
    error={<>Custom error, <Link href="https://docs.customer.io" isExternal={true} variant="secondary">see docs for help</Link></>}
    inputValue={fieldState.inputValue}
    selectedValue={fieldState.selectedValue}
    onValueChange={(value) => {
      setFieldState({
        inputValue: value,
        selectedValue: value === '' ? null : fieldState.selectedValue,
      });
    }}
    onSelectionChange={(value) => {
      setFieldState({
        inputValue: value != null ? simpleOptions.find((o) => o.value === value)?.label ?? '' : '',
        selectedValue: value,
      });
    }}
    options={simpleOptions}
  />
}

API

Element (or elements) that describe the form field. Also used for error messages, as aria-errormessage isn't supported in all assistive technologies: https://a11ysupport.io/tech/aria/aria-errormessage_attribute

String value that labels the form field.

Element (or elements) that label the form field.

Focuses automatically when the component is rendered. Should be used with care and only sparingly, as there are some accessibility concerns. More information here

Additional classes to be applied to the top-level container. This is necessary as an argument in Ember, where we apply splattributes on the "input" itself, which means we can't pass a custom class to the containing element the classic way.

A description for the field, providing additional context or hints.

An error message associated with the form field. Boolean: Ember-only. If the error named block is used, this prop can be set to true to indicate an error is present, which will make the error block render.

The component to render at the footer of the menu.

The component to render at the header of the menu (before the search input, if present).

The name of an icon (from Pluma Icons) to be rendered on the left side of the input.

An optional id to be assigned to the input element. Also used to generate ids for labels and error messages. If one isn't provided, it will be generated.

The current string value in the text field.

Whether the input element is clearable.

Whether the form field is disabled.

Whether the form field is in an invalid state.

Whether the input should show a loading spinner.

Whether more options are currently being loaded.

The label text to show with the form field.

Default:125

The duration (in ms) for the dropdown menu popover animation.

middlewareOptions  { offset?: Partial<{ mainAxis: number; crossAxis: number; alignmentAxis: number | null; }> | ((state: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; }) => Partial<{ mainAxis: number; crossAxis: number; alignmentAxis: number | null; }>) | undefined; autoPlacement?: { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; crossAxis?: boolean | undefined; alignment?: Alignment | null | undefined; autoAlignment?: boolean | undefined; allowedPlacements?: Placement[] | undefined; boundary?: Boundary | undefined; } | ((state: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; }) => { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; crossAxis?: boolean | undefined; alignment?: Alignment | null | undefined; autoAlignment?: boolean | undefined; allowedPlacements?: Placement[] | undefined; boundary?: Boundary | undefined; }) | undefined; shift?: { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; mainAxis?: boolean | undefined; crossAxis?: boolean | undefined; limiter?: { fn: (state: MiddlewareState) => Coords; options?: any; } | undefined; boundary?: Boundary | undefined; } | ((state: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; }) => { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; mainAxis?: boolean | undefined; crossAxis?: boolean | undefined; limiter?: { fn: (state: MiddlewareState) => Coords; options?: any; } | undefined; boundary?: Boundary | undefined; }) | undefined; hide?: { padding?: Padding | undefined; strategy?: "referenceHidden" | "escaped" | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; boundary?: Boundary | undefined; } | ((state: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; }) => { padding?: Padding | undefined; strategy?: "referenceHidden" | "escaped" | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; boundary?: Boundary | undefined; }) | undefined; size?: { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; boundary?: Boundary | undefined; apply?: ((args: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; } & { availableWidth: number; availableHeight: number; }) => Promisable<void>) | undefined; } | ((state: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; }) => { padding?: Padding | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; boundary?: Boundary | undefined; apply?: ((args: { x: number; y: number; placement: Placement; strategy: Strategy; initialPlacement: Placement; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; } & { availableWidth: number; availableHeight: number; }) => Promisable<void>) | undefined; }) | undefined; } | undefined

Additional options to pass into Floating UI middleware

The name attribute is used for HTML forms

Called when the active (currently highlighted, but not necessarily selected) option changes when the dropdown is open.

In addition to isClearable, this function is required to render the clear button. This will be called when the clear button is clicked.

Called when the dropdown finishes closing (after the close animation finishes).

Callback for when the end of the dropdown is reached and more options should be loaded.

Called when the dropdown is opened.

Callback when the open state should change.

The callback for when an option is selected.

The callback for when the input's value changes.

The component to render for each option in the menu.

Placeholder text to show inside the field.

The current selected option value (the value of the selected option item).

Whether to allow browser autofill and password managers. When false (default), adds attributes to disable password managers (1Password, LastPass, Bitwarden, Dashlane). Set to true for fields where autofill is desired (e.g., email, password, address fields).

Whether the generated description id should be included in the input's aria-describedby attribute. Disable this when a label`` tag surrounds the input as well as it's description text, for example in an OptionCard` component.

Whether the generated label id should be included in the input's aria-labelledby attribute. Disable this when the label tag surrounds the input as well as it's label text, for example in an OptionCard component.

Default:true

Whether the component should filter the options based on the input value. Set to false if you want to handle the filtering yourself, for example when the options are fetched and filtered via an API.

Whether the onLoadMore callback should be called when the end of the dropdown is reached. If no more paginated options are available, this should be set to false.

Default:false

Whether the dropdown should open when the input is focused.

By default, form elements will throw an error if no label or aria-label is provided. Disable this to suppress the error, for example when building a custom form element, and you want to handle labeling yourself.

The size of the input element.

Additional classes to be applied to the description element.

Additional classes to be applied to the error element.

Additional classes to be applied to the field node.

Additional classes to be applied to the footer element.

Ember-only: this is used internally to detect whether a label named block is present, and to ignore empty label props if so.

Additional classes to be applied to the input node.

Sets the size attribute on the input element. This controls the visible width of the input in characters.

Whether the component should render with "legacy" styles.

Additional classes to be applied to the label node.

Whether to apply the center-baseline variant.

Show tooltip only when label is truncated, for all options and group headers. Can be overridden per-option or per-group.

Whether the menu options should render in a virtualized list.