A Select control built with and for ReactJS that provides comprehensive dropdown functionality with support for single/multi-select, async data loading, search filtering, and extensive customization.
—
React-Select provides comprehensive TypeScript support with generic types, interfaces, and utility types that ensure type safety throughout the component system. All interfaces support generic Option and Group types for maximum flexibility.
Fundamental type definitions for options, values, and data structures.
/**
* Base interface for option groups containing multiple options
* @template Option - The option data type
*/
interface GroupBase<Option> {
/** Array of options within this group */
readonly options: readonly Option[];
/** Optional label for the group header */
readonly label?: string;
}
/**
* Union type for arrays containing options or groups
* @template Option - The option data type
* @template Group - The group data type extending GroupBase<Option>
*/
type OptionsOrGroups<Option, Group extends GroupBase<Option>> = readonly (Option | Group)[];
/**
* Simple array of options without grouping
* @template Option - The option data type
*/
type Options<Option> = readonly Option[];
/**
* Single value type - an option or null for no selection
* @template Option - The option data type
*/
type SingleValue<Option> = Option | null;
/**
* Multi-value type - readonly array of selected options
* @template Option - The option data type
*/
type MultiValue<Option> = readonly Option[];
/**
* Union type for any value prop - single or multi
* @template Option - The option data type
*/
type PropsValue<Option> = MultiValue<Option> | SingleValue<Option>;
/**
* Conditional type for onChange values based on isMulti flag
* @template Option - The option data type
* @template IsMulti - Boolean flag indicating multi-select mode
*/
type OnChangeValue<Option, IsMulti extends boolean> =
IsMulti extends true ? MultiValue<Option> : SingleValue<Option>;Usage Examples:
// Basic option type
interface ColorOption {
value: string;
label: string;
color: string;
}
// Grouped options
interface ColorGroup extends GroupBase<ColorOption> {
label: string;
options: readonly ColorOption[];
}
const colorGroups: OptionsOrGroups<ColorOption, ColorGroup> = [
{
label: "Primary Colors",
options: [
{ value: "red", label: "Red", color: "#ff0000" },
{ value: "blue", label: "Blue", color: "#0000ff" },
{ value: "yellow", label: "Yellow", color: "#ffff00" },
],
},
{
label: "Secondary Colors",
options: [
{ value: "green", label: "Green", color: "#00ff00" },
{ value: "orange", label: "Orange", color: "#ffa500" },
{ value: "purple", label: "Purple", color: "#800080" },
],
},
];
// Typed select usage
const TypedSelect = () => {
const [value, setValue] = useState<SingleValue<ColorOption>>(null);
return (
<Select<ColorOption, false, ColorGroup>
value={value}
onChange={setValue}
options={colorGroups}
/>
);
};Types for handling change events and understanding what triggered them.
/**
* Action metadata provided to onChange handlers
* @template Option - The option data type
*/
interface ActionMeta<Option> {
/** The action that triggered the change */
action:
| 'select-option' // Option was selected
| 'deselect-option' // Option was deselected (multi-select)
| 'remove-value' // Value was removed (multi-select)
| 'pop-value' // Last value was removed via backspace
| 'set-value' // Value was set programmatically
| 'clear' // All values were cleared
| 'create-option'; // New option was created (CreatableSelect)
/** Form field name if provided */
name?: string;
/** The option involved in the action (for select/deselect/create) */
option?: Option;
/** Single removed value (for remove-value action) */
removedValue?: Option;
/** Multiple removed values (for clear action) */
removedValues?: Option[];
}
/**
* Input change action metadata
*/
interface InputActionMeta {
/** The action that triggered the input change */
action:
| 'set-value' // Input value set programmatically
| 'input-change' // User typed in input
| 'input-blur' // Input lost focus
| 'menu-close'; // Menu was closed
/** Previous input value */
prevInputValue: string;
}
/**
* Focus direction for keyboard navigation
*/
type FocusDirection = 'up' | 'down' | 'pageup' | 'pagedown' | 'first' | 'last';
/**
* Menu placement options
*/
type MenuPlacement = 'auto' | 'bottom' | 'top';
/**
* Menu CSS position
*/
type MenuPosition = 'absolute' | 'fixed';
/**
* Set value action type for programmatic changes
*/
type SetValueAction = 'set-value';Usage Examples:
// Handling different action types
const handleChange = (
newValue: OnChangeValue<Option, false>,
actionMeta: ActionMeta<Option>
) => {
console.log(`Action: ${actionMeta.action}`);
switch (actionMeta.action) {
case 'select-option':
console.log('Selected:', actionMeta.option);
break;
case 'clear':
console.log('Cleared all values:', actionMeta.removedValues);
break;
case 'remove-value':
console.log('Removed:', actionMeta.removedValue);
break;
case 'create-option':
console.log('Created new option:', actionMeta.option);
// Save to database or update options list
break;
}
setValue(newValue);
};
// Input change handling
const handleInputChange = (
inputValue: string,
actionMeta: InputActionMeta
) => {
if (actionMeta.action === 'input-change') {
// Handle user typing
setInputValue(inputValue);
}
};Complete type definitions for all component props interfaces.
/**
* Props for the main Control component
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface ControlProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** Whether the control is focused */
isFocused: boolean;
/** Whether the control is disabled */
isDisabled: boolean;
/** Child elements (ValueContainer, IndicatorsContainer) */
children: ReactNode;
/** Additional inner props passed to the control element */
innerProps: JSX.IntrinsicElements['div'];
}
/**
* Props for individual Option components
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface OptionProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** The option data */
data: Option;
/** Whether this option is disabled */
isDisabled: boolean;
/** Whether this option is focused */
isFocused: boolean;
/** Whether this option is selected */
isSelected: boolean;
/** Child content (usually the option label) */
children: ReactNode;
/** Additional inner props passed to the option element */
innerProps: JSX.IntrinsicElements['div'];
}
/**
* Props for Menu component
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface MenuProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** Child elements (MenuList) */
children: ReactNode;
/** Additional inner props passed to the menu element */
innerProps: JSX.IntrinsicElements['div'];
/** Menu placement */
placement: MenuPlacement;
}
/**
* Props for MultiValue components
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface MultiValueProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** The option data for this multi-value */
data: Option;
/** Child elements (MultiValueContainer) */
children: ReactNode;
/** Index of this value in the values array */
index: number;
/** Whether this multi-value is disabled */
isDisabled: boolean;
/** Function to remove this value */
removeProps: {
onClick: MouseEventHandler<HTMLDivElement>;
onTouchEnd: TouchEventHandler<HTMLDivElement>;
onMouseDown: MouseEventHandler<HTMLDivElement>;
};
}
/**
* Props for SingleValue component
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface SingleValueProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** The selected option data */
data: Option;
/** Whether the value is disabled */
isDisabled: boolean;
/** Child content (usually the option label) */
children: ReactNode;
}
/**
* Props for Input component
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface InputProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>>
extends CommonPropsAndClassName<Option, IsMulti, Group> {
/** Current input value */
value: string;
/** Auto-complete attribute */
autoComplete: string;
/** Input ID */
id: string;
/** Whether the input is disabled */
isDisabled: boolean;
/** Whether the input is hidden */
isHidden: boolean;
/** Additional props passed to the input element */
innerProps?: JSX.IntrinsicElements['input'];
}Types for ARIA support and accessibility features.
/**
* Configuration for ARIA live region messages
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface AriaLiveMessages<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
/** Message when guidance should be provided */
guidance?: (props: AriaGuidanceProps) => string;
/** Message when selection changes */
onChange?: (props: AriaOnChangeProps<Option, IsMulti>) => string;
/** Message when options are filtered */
onFilter?: (props: AriaOnFilterProps<Option>) => string;
/** Message when focus changes */
onFocus?: (props: AriaOnFocusProps<Option>) => string;
}
/**
* Props for guidance messages
*/
interface AriaGuidanceProps {
/** Whether the component is searchable */
isSearchable: boolean;
/** Whether multiple selection is enabled */
isMulti: boolean;
/** Current selection count */
selectionCount: number;
/** Tab index value */
tabSelectsValue: boolean;
}
/**
* Props for change announcement messages
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
*/
interface AriaOnChangeProps<Option, IsMulti extends boolean> {
/** The action that occurred */
action: ActionMeta<Option>['action'];
/** Label of affected option */
label?: string;
/** Labels of all affected options */
labels?: string[];
/** Current value after change */
value: string;
}
/**
* Props for focus announcement messages
* @template Option - The option data type
*/
interface AriaOnFocusProps<Option> {
/** Currently focused option */
focused: Option;
/** Label of focused option */
label: string;
/** Whether the option is selected */
isSelected: boolean;
/** Whether the option is disabled */
isDisabled: boolean;
}
/**
* Props for filter announcement messages
* @template Option - The option data type
*/
interface AriaOnFilterProps<Option> {
/** Current input value */
inputValue: string;
/** Array of filtered results */
resultsMessage: string;
}Helper types for advanced use cases and type manipulation.
/**
* Extract the Option type from a Select component type
*/
type OptionTypeBase = {
label: ReactNode;
value: string | number;
isDisabled?: boolean;
};
/**
* Common props shared across all components
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
interface CommonPropsAndClassName<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
/** Additional CSS class name */
className?: string;
/** CSS class name prefix for BEM-style naming */
classNamePrefix?: string;
/** Component configuration */
selectProps: Props<Option, IsMulti, Group>;
}
/**
* Function type for getting option labels
* @template Option - The option data type
*/
type GetOptionLabel<Option> = (option: Option) => string;
/**
* Function type for getting option values
* @template Option - The option data type
*/
type GetOptionValue<Option> = (option: Option) => string;
/**
* Function type for checking if option is disabled
* @template Option - The option data type
*/
type IsOptionDisabled<Option> = (option: Option, selectValue: Options<Option>) => boolean;
/**
* Filter option with additional metadata
* @template Option - The option data type
*/
interface FilterOptionOption<Option> {
label: string;
value: string;
data: Option;
}
/**
* Function type for custom filtering
* @template Option - The option data type
*/
type FilterOptionFunction<Option> = (
option: FilterOptionOption<Option>,
inputValue: string
) => boolean;Usage Examples:
// Custom option type with additional metadata
interface UserOption extends OptionTypeBase {
value: string;
label: string;
email: string;
avatar: string;
role: 'admin' | 'user' | 'guest';
isDisabled?: boolean;
}
// Utility functions with proper typing
const getUserLabel: GetOptionLabel<UserOption> = (user) => user.label;
const getUserValue: GetOptionValue<UserOption> = (user) => user.value;
const isUserDisabled: IsOptionDisabled<UserOption> = (user) => user.role === 'guest';
// Custom component with proper typing
const UserOption: React.FC<OptionProps<UserOption, false, GroupBase<UserOption>>> = ({
data,
children,
isSelected,
isFocused,
...props
}) => (
<components.Option {...props} data={data} isSelected={isSelected} isFocused={isFocused}>
<div className="user-option">
<img src={data.avatar} alt="" className="avatar" />
<div>
<div className="name">{children}</div>
<div className="email">{data.email}</div>
<div className="role">{data.role}</div>
</div>
</div>
</components.Option>
);
// Typed select with custom option
const UserSelect = () => (
<Select<UserOption, false, GroupBase<UserOption>>
options={users}
getOptionLabel={getUserLabel}
getOptionValue={getUserValue}
isOptionDisabled={isUserDisabled}
components={{ Option: UserOption }}
/>
);Complex type utilities for advanced customization scenarios.
/**
* Extract component props type from SelectComponentsConfig
* @template K - The component key
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
type ComponentProps<
K extends keyof SelectComponentsConfig<any, any, any>,
Option,
IsMulti extends boolean,
Group extends GroupBase<Option>
> = SelectComponentsConfig<Option, IsMulti, Group>[K] extends ComponentType<infer P> ? P : never;
/**
* Utility type for creating themed style functions
* @template T - The component props type
*/
type ThemedStyleFunction<T> = (base: CSSObject, state: T, theme: Theme) => CSSObject;
/**
* Helper type for creating strongly-typed component factories
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
type SelectComponentFactory<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = {
[K in keyof SelectComponentsConfig<Option, IsMulti, Group>]: ComponentType<
ComponentProps<K, Option, IsMulti, Group>
>;
};
/**
* Utility type for partial component configurations
* @template Option - The option data type
* @template IsMulti - Boolean flag for multi-select
* @template Group - The group data type
*/
type PartialSelectComponents<Option, IsMulti extends boolean, Group extends GroupBase<Option>> =
Partial<SelectComponentsConfig<Option, IsMulti, Group>>;Usage Examples:
// Strongly-typed component factory
const createComponents = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>():
SelectComponentFactory<Option, IsMulti, Group> => ({
Option: (props: ComponentProps<'Option', Option, IsMulti, Group>) => (
<div {...props.innerProps}>
{props.children}
</div>
),
Control: (props: ComponentProps<'Control', Option, IsMulti, Group>) => (
<div {...props.innerProps}>
{props.children}
</div>
),
// ... other components
});
// Themed style utilities
const createThemedStyles = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>() => ({
control: ((base, state, theme) => ({
...base,
borderColor: state.isFocused ? theme.colors.primary : theme.colors.neutral20,
boxShadow: state.isFocused ? `0 0 0 1px ${theme.colors.primary}` : 'none',
})) as ThemedStyleFunction<ControlProps<Option, IsMulti, Group>>,
option: ((base, state, theme) => ({
...base,
backgroundColor: state.isSelected
? theme.colors.primary
: state.isFocused
? theme.colors.primary25
: 'transparent',
color: state.isSelected ? 'white' : theme.colors.neutral80,
})) as ThemedStyleFunction<OptionProps<Option, IsMulti, Group>>,
});Install with Tessl CLI
npx tessl i tessl/npm-react-select