Select
The Select component is a customizable dropdown input that allows users to select one or multiple options from a list. It supports features like search, custom option rendering, and integration with icons and other UI elements.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select a Fruit" options={[ { value: "apple", title: "Apple" }, { value: "banana", title: "Banana" }, { value: "cherry", title: "Cherry" }, ]} value={value} onChange={setValue} /> ); }
Features
Multiple Selection
To enable multiple selection, set the multiple prop to true and manage an array of selected values:
() => { const [value, setValue] = React.useState<string[]>([]); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select Fruits" options={[ { value: "apple", title: "Apple" }, { value: "banana", title: "Banana" }, { value: "cherry", title: "Cherry" }, ]} value={value} onChange={setValue} multiple /> ); }
Leading and Trailing Elements
You can add leading and trailing elements to the Select component, such as an icon or a clear button.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select a City" leading={<Icon.Place opacity={500} />} options={[ { value: "amsterdam", title: "Amsterdam" }, { value: "paris", title: "Paris" }, { value: "london", title: "London" }, ]} value={value} onChange={setValue} /> ); }
Detailed Options
You can provide additional attributes to each option, such as a subtitle and leading or trailing elements.
export interface Option {
value: string;
title?: Text;
subtitle?: Text;
leading?: ReactNode;
trailing?: ReactNode;
}Below is a simple example with subtitles and leading elements.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select a Celestial Body" options={[ { value: "star", title: "Star", subtitle: "Luminous sphere of plasma.", trailing: <Icon.LightMode />, }, { value: "planet", title: "Planet", subtitle: "Chunk of matter orbiting a star.", trailing: <Icon.Public />, }, { value: "moon", title: "Moon", subtitle: "Natural satellite of a planet.", trailing: <Icon.DarkMode />, }, ]} value={value} onChange={setValue} /> ); }
Custom Options
You can also add your own attributes to your options. Although the default Option component will not make use of these, you can provide your own implementation of the Option component to display the additional information or customize the layout of each option.
The example below uses the original Option component, but changes how its subtitle and trailing elements are displayed using some custom Option attributes.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select an Airport" options={[ { value: "AMS", title: "Schiphol", city: "Amsterdam", country: "Netherlands" }, { value: "LCH", title: "Heathrow", city: "London", country: "United Kingdom" }, { value: "CDG", title: "Charles de Gaulle", city: "Paris", country: "France" }, ]} Option={({ city, country, ...props }) => ( <Option {...props} subtitle={`${city}, ${country}`} trailing={<Chip label={props.value} />} /> )} value={value} onChange={setValue} /> ); }
The component you pass to theOption prop automatically receives all of the custom attributes you provide to your options as props.
With Type Declaration
For extra type-safety, it is recommended to create an interface for your custom option. If you follow the steps below, you will have a robust and sustainable solution.
First, define your custom option interface extending Option.
interface AirportOption extends Omit<Option, "subtitle"> {
city: string;
country: string;
}Then, declare your custom options using the custom interface.
const airportOptions: AirportOption[] = [
{
value: "AMS",
title: "Schiphol",
city: "Amsterdam",
country: "Netherlands",
},
{
value: "CDG",
title: "Charles de Gaulle",
city: "Paris",
country: "France",
},
];Next, create a custom Option component that accepts the custom option interface by providing a type argument to the OptionProps generic.
function AirportOption({
city,
country,
...props
}: OptionProps<AirportOption>) {
return (
<Option
{...props}
subtitle={`${city}, ${country}`}
trailing={<Chip label={props.value} />}
/>
);
}Finally, provide the custom options and your Option component to the Select component.
<Select
placeholder="Select an Airport"
options={airportOptions}
value={value}
onChange={setValue}
Option={AirportOption}
/>Custom Indicators
Next to using custom attributes, another common use-case for overriding the Option component is to customize the indicator displayed for selected options.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Select Favorites" options={[ { value: "apple", title: "Apple" }, { value: "banana", title: "Banana" }, { value: "cherry", title: "Cherry" }, ]} value={value} onChange={setValue} multiple Option={({ isSelected, ...props }) => ( <Option {...props} trailing={isSelected ? <Icon.Favorite /> : <Icon.FavoriteBorder />} /> )} /> ); }
The Select component will automatically forward all of your custom option attributes to the Option component props in a type-safe manner.
Search
Enabling Search
To enable search functionality within the dropdown, set the search prop to true.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Search and Select" options={[ { value: "apple", title: "Apple" }, { value: "banana", title: "Banana" }, { value: "cherry", title: "Cherry" }, ]} value={value} onChange={setValue} search /> ); }
By default, the search query is matched against the option’s title and subtitle. The options are then sorted based on the score, with the most relevant option displayed first.
Search Score and Matches
The search functionality uses a scoring algorithm to rank the options based on the search query. Since the score represents a penalty, the lowest scoring option is the most relevant.
If you want to make use of the score and matches, you can enable their inclusion in the option props and use them in your custom option component.
<Select
// ...
search={{
includeScore: true,
includeMatches: true,
}}
Option={({ score, matches, ...props }) => (
<Option
{...props}
title={<Highlight text={title} matches={matches} />}
tailing={<Chip label={score.toFixed(2)} />}
/>
)}
/>Custom Search Keys
By default, the search query is matched against the option’s title and subtitle. If you want to search against additional keys, regardless of whether those keys are displayed in the option, you can provide a custom search keys array.
() => { const [value, setValue] = React.useState<string>(); return ( <Select constraints={{ maxWidth: 700 }} placeholder="Search and Select" options={[ { value: "AMS", title: "Schiphol", city: "Amsterdam", country: "Netherlands" }, { value: "LCH", title: "Heathrow", city: "London", country: "United Kingdom" }, { value: "CDG", title: "Charles de Gaulle", city: "Paris", country: "France" }, ]} value={value} onChange={setValue} search={{ keys: ["value", "title", "city", "country"], }} /> ); }
It is also possible to provide weights for each key to adjust the search relevance.
<Select
// ...
search={{
keys: [
{ name: "value", weight: 1 },
{ name: "title", weight: 1 },
{ name: "city", weight: 0.5 },
{ name: "country", weight: 0.25 },
],
}}
/>Anatomy
Reference
SelectProps
| Name | Type | Required | Decription |
|---|---|---|---|
options | Option[] | true | An array of option objects. |
value | string | string[] | true | The current selected options. |
onChange | (value: string | string[]) => void | true | Callback when selection changes. |
placeholder | string | false | Placeholder text when no option is selected. |
multiple | boolean | false | Enables multiple selection when true. |
search | boolean | FuseOptions | false | Enables search within options when true. |
leading | ReactNode | false | Element displayed at the start of the select input. |
trailing | ReactNode | false | Element displayed at the end of the select input. |
Option | ComponentType<OptionProps> | false | Custom component for rendering each option. |
Option
| Name | Type | Required | Decription |
|---|---|---|---|
value | string | true | The value of the option. |
title | Text | false | Display text for the option. |
subtitle | Text | false | Secondary text for the option. |
leading | ReactNode | false | Element displayed at the start of the option. |
trailing | ReactNode | false | Element displayed at the end of the option. |
FuseOptions
Please refer to the Fuse.js Documentation .
Tokens
| Key | Default Value |
|---|---|
row.base.color | inherit |
row.base.backgroundColor | var(--_1i4ikbk10) |
row.base.paddingLeft | var(--gpqiqd7) |
row.base.paddingRight | var(--gpqiqd7) |
row.base.paddingTop | var(--gpqiqd7) |
row.base.paddingBottom | var(--gpqiqd7) |
row.base.borderWidth | var(--gpqiqbf) |
row.base.borderStyle | solid |
row.base.borderColor | var(--_1i4ikbkx) |
row.base.borderRadius | var(--gpqiqdv) |
row.base.gap | var(--gpqiqd7) |
row.base.:hover.borderColor | var(--_1i4ikbky) |
row.base.:focus-within.borderColor | var(--_1i4ikbky) |
row.base.:active.borderColor | var(--_1i4ikbky) |
row.variants.error.true.backgroundColor | var(--_1i4ikbki) |
row.variants.error.true.borderColor | var(--_1i4ikbkg) |
row.variants.error.true.color | var(--_1i4ikbkg) |
row.variants.error.true.:hover.backgroundColor | var(--_1i4ikbki) |
row.variants.error.true.:hover.borderColor | var(--_1i4ikbkg) |
row.variants.error.true.:hover.color | var(--_1i4ikbkg) |
row.variants.error.true.:focus-visible.backgroundColor | var(--_1i4ikbki) |
row.variants.error.true.:focus-visible.borderColor | var(--_1i4ikbkg) |
row.variants.error.true.:focus-visible.color | var(--_1i4ikbkg) |
row.variants.error.true.:active.backgroundColor | var(--_1i4ikbki) |
row.variants.error.true.:active.borderColor | var(--_1i4ikbkg) |
row.variants.error.true.:active.color | var(--_1i4ikbkg) |
row.variants.error.true.:disabled.backgroundColor | var(--_1i4ikbki) |
row.variants.error.true.:disabled.borderColor | var(--_1i4ikbkg) |
row.variants.error.true.:disabled.color | var(--_1i4ikbkg) |
row.variants.disabled.true.opacity | var(--_1i4ikbk2i) |
input.base.font | var(--_1i4ikbk25) |
input.base.paddingLeft | 0 |
input.base.paddingRight | 0 |
input.base.paddingTop | 0 |
input.base.paddingBottom | 0 |
popover.base.backgroundColor | var(--_1i4ikbk10) |
popover.base.borderRadius | var(--gpqiqdv) |
popover.base.borderWidth | var(--gpqiqbf) |
popover.base.borderStyle | solid |
popover.base.borderColor | var(--_1i4ikbkx) |
option.base.paddingLeft | var(--gpqiqd7) |
option.base.paddingTop | var(--gpqiqd7) |
option.base.paddingRight | var(--gpqiqd7) |
option.base.paddingBottom | var(--gpqiqd7) |
option.base.borderRadius | 0 |
option.base.backgroundColor | var(--_1i4ikbk10) |
option.base.color | var(--_1i4ikbkv) |
option.base.gap | var(--gpqiqd7) |
option.base.:hover.backgroundColor | color-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%) |
option.base.:hover.color | var(--_1i4ikbkv) |
option.base.:focus-visible.backgroundColor | color-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%) |
option.base.:focus-visible.color | var(--_1i4ikbkv) |
option.base.:active.backgroundColor | color-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%) |
option.base.:active.color | var(--_1i4ikbkv) |