Select
Select allows the user to choose one or more items from a list. The selected item can represent a value in a form or be used to filter or sort existing content.
#Similar components with different purposes
To clarify the differences between Select
and ActionMenu
, a short description is provided.
Component name | Usage |
---|---|
Select | Choose one or more values from a list. |
Action Menu | A list of individual actions that the user can perform and links. |
#Examples
#Single Select
A single Select
allows the user to choose only one item from a list of mutually exclusive options. If you have more than 7 items, you should use a Select
. If you have 3-6 items, you can use either Radios
or a Select
.
A default Select
consists of a button, a down-arrow icon, a list box, and a clear all button that is only displayed when an option is selected.
- Label: when using a standalone
Select
, you should always have a label. A label text should inform the user what to select from a list of options. Use Form Element Wrapper around aSelect
to get the proper label. - Button: the user can click the button to show a list of items. After selection, the item is displayed in the button.
- Down-arrow icon: indicates if the list box is shown or not.
- List box: contains a list of selectable items that can be grouped into categories and contain an icon, description, or both (see Items Variants below). If you select an item or click outside the list box, it will be hidden.
- Clear all button: the user can remove selected items by clicking the X icon.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);
#Multi Select
A multi Select
allows the user to select or deselect one or more items.
A multi Select
includes all of the elements from a single Select
with the following additions:
- Badge: shows the number of selected items.
- Checkmark icon: indicates which item is currently selected in the list.
- Footer: the user can confirm the selection or click the “Cancel” button to reset the selection.
Use array-like values to enable the multi select mode.
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);
#Searchable
The search field allows the user to type a keyword to search for an option. As the user types, the searchable
Select
filters the results. This helps the user find something quickly in a large list of options. For example, a searchable
Select
is most often used for filtering a list of countries, as it is extensive, but also easy to answer.
Use a searchable
Select
when
- the user needs to filter a large list of items.
- the user can quickly find a known option.
For developers
You can use the searchable
to control whether or not to display a search bar above the list box. If set to auto
(the default), the search bar will be visible only when there are more than 7 items. If set to always
, it will be visible no matter the total amount of options. And, if set to never
, it will not be displayed.
By default, the search bar will search for the title
property of the options. However, you can specify custom searchable content to search for by using the searchableContent
prop. This prop should be a function that takes an option as an argument and returns a string that will be used to match the search query. You can see it in action in the Groups example, where the searchable content takes both the option title and description.
For more complex use cases, you can use the onSearch
property which is a function that takes a query string as an argument. In this case, the developer is responsible for using this query string to fetch new items and update the options list. Also, the developer must manage when to include the selected options in the list. You can see it in action in the Async examples.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
searchable="always"
/>
);
#Common Use Cases
#With default option
Whenever possible, give the user the optimal option by default. This will help you avoid cases where the alternative options can often lead to errors or be confusing for inexperienced users.
You can specify an option to be selected by default using the defaultOption
property. If this property is not provided or is undefined, the first option will be selected by default in single select mode.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of options"
items={[
{ title: "All", value: "all" },
{ title: "Option 1", value: "option-1" },
{ title: "Option 2", value: "option-2" },
]}
value={selectedOption}
onChange={setSelectedOption}
defaultOption="all"
/>
);
#Without default option
You can use the noDefaultOption
property to not select an option by default. In this case, when the selection is empty, the placeholder will be displayed in the Select
button content.
For the Select
without default option, a label should be provided to the user. A label should clearly describe the purpose of the selection.
Placeholder text
A placeholder text can serve as a hint, description, or an example of the information required for a particular field.
However, placeholder text should not be used as a label, since it disappears when an item is selected. In most cases, the user will forget what information belongs in a field. As a result, the user with visual and cognitive impairments are faced with an additional burden.
const [selectedOption, setSelectedOption] = useState<number | undefined>();
return (
<Select
aria-label="List of options"
placeholder="Choose one"
items={[
{ title: "Option 1", value: 1 },
{ title: "Option 2", value: 2 },
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);
With custom keys
In simple terms, the select list box functions as an array of options. As explained in the React documentation, each item in the list requires a key
prop, which must be unique. Without this key, the component defaults to using the option's title, which can cause issues if titles aren't unique. If ensuring title uniqueness isn't feasible, it's crucial to assign a unique key to each option, as shown in the example below.
const [selectedOption, setSelectedOption] = useState<number | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{
title: "Apple",
description: "Red color",
value: 1,
key: "red-apple",
},
{
title: "Apple",
description: "Green color",
value: 2,
key: "green-apple",
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);
#With TypeScript union
type Fruits = "apple" | "banana" | "blueberry" | "cherry";
const [selectedOption, setSelectedOption] = useState<Fruits | undefined>();
return (
<Select
aria-label="List of fruits"
placeholder="Choose a fruit"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);
#Bulk Actions
When many options need to be selected, it is recommended to include "Select all" and "Deselect all" buttons upfront to provide flexibility to the user and speed up the bulk selection process.
The following examples show the number of selected/deselected items:
- Select all (2): indicates that two options in the list were not selected. The user can select the remaining two selectable items at once.
- Deselect all (3): indicates that three options are already selected in the list. The user can deselect all three selected options at once.
Note the "Select all" button is disabled once all items are selected. The "Deselect all" button is only available if the user has at least one item selected. Otherwise, the button is disabled.
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
bulkActions
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);
#Max Number of Items
A badge and hint text indicate the number of items that can be selected in advance. Providing helpful constraints avoids user errors. For example, the user can immediately select the correct number of items instead of receiving an error message after submitting the form.
Note that after selecting the maximum number of items, the other items are disabled. The user must deselect one of the selected items to be able to select another item.
const items = [
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
];
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
items={items}
value={selectedOptions}
onChange={setSelectedOptions}
maxNumberOfItems={2}
/>
);
#Creatable
Use creatable Select
to allow the user to add a new item to the list for selection. You must also provide the onCreate
function that will be called when the user clicks the create button. This function takes the value present on the search field as an argument and must return a new option (type of OptionItem
) to be added to the list. The new option will be selected automatically after creating it. You can also change the label of the create button by using thecreateButtonLabel
prop.
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [selectedOption, setSelectedOption] = useState<string[]>([]);
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
return (
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue].sort((a, b) => a.localeCompare(b)));
return itemize(newValue);
}}
/>
);
#With validation
By default, the list box will be hidden after creating a new option in single select mode and kept open in multi select mode. However, you can control this behavior by returning a boolean in the onCreate
function, where false
means closing the list box. This is useful when you want to validate the new option before adding it to the list. Here is an example of how to do it:
Pick or create a fruit
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [isValid, setIsValid] = useState(true);
const [selectedOption, setSelectedOption] = useState<string[]>([]);
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
const validate = (value: string) => {
return value.length > 0 && value.length < 10;
};
return (
<FormElementWrapper
label="Fruit"
name="Fruit"
invalid={!isValid}
error="Value must be between 1 and 10 characters"
helptext="Pick or create a fruit"
>
<Select
aria-label="List of fruits"
creatable
items={items.map(itemize)}
value={selectedOption}
onChange={(newValue) => {
setSelectedOption(newValue);
setIsValid(true);
}}
onCreate={(value: string) => {
const trimmedValue = value.trim();
const isValidValue = validate(trimmedValue);
if (!isValidValue) {
setIsValid(false);
return false;
}
setIsValid(true);
setItems([...items, trimmedValue].sort((a, b) => a.localeCompare(b)));
return itemize(trimmedValue);
}}
/>
</FormElementWrapper>
);
#Item variants
A selectable item can be provided with an icon and description to give additional context or instructions about what a user should select. Keep items concise and consistent.
- Title only: the text describes the selectable item. Long items where the text is divided into multiple lines are not recommended.
- Title and description: provide additional context for the item, no longer than one line.
- Title, description, and icon: helps the user immediately understand the item. An icon must convey the meaning of the item and should not be used for decorative purposes.
State variations
Each item has the following states:
- Default: the default state is the start or end state, indicating whether the
Select
button is empty or filled. - Hover: when a user hovers over an item, a highlight (light gray background) appears indicating that the item is selectable.
- Focus: a blue outline appears around the text area when the user tabs or uses the keyboard to navigate through the items.
- Active/Pressed: a light blue background appears while the item is clicked.
- Selected: the left border and checkmark icon indicate which item in the list is currently selected.
- Disabled: the disabled option should be used infrequently. It indicates that the option is present, but is not currently available to the user.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple-1" },
{ title: "Apple disabled", value: "apple-2", disabled: true },
{
title: "Apple with description",
description: "Round and usually red or green.",
value: "apple-3",
},
{
title: "Apple with icon",
value: "apple-4",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Apple with icon and description",
description: "Round and usually red or green.",
value: "apple-5",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Apple with icon and description disabled",
value: "apple-6",
description: "Round and usually red or green.",
icon: (
<Icon>
<IconLink />
</Icon>
),
disabled: true,
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);
#Groups
Group related items in the list to give the user an overview of the list. Empathize with the user. Seeing a list of ungrouped options, can make it hard for the user to find the option they are looking for. Avoid having only one group in a list box.
const [selectedOption, setSelectedOption] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
searchable="always"
searchableContent={(item) => item.title + " " + item.description}
items={[
{
heading: "Fruits",
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
},
{
heading: "Vegetables",
items: [
{
title: "Aspargus",
description: "Easily recognizable for its long pointy spears.",
value: "aspargus",
},
{
title: "Carrot",
description: "Orange in color, but purple, black, red, white, and yellow also exist.",
value: "carrot",
},
{
title: "Cocumber",
description: "Usually considered a vegetable, however it's botanically a fruit.",
value: "cocumber",
},
],
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);
#Async
Use async Select
to load asynchronous data from a remote source, either while loading or each time the value changes.
For the examples below, consider the following API:
type Fruit = {
id: number;
name: string;
};
type API = {
fruits: Fruit[];
};
const api: API = {
fruits: [
{ id: 0, name: "Apple" },
{ id: 1, name: "Banana" },
//...
{ id: 14, name: "Watermelon" },
],
};
The entire options list can be fetched asynchronously and stored in a state. You can use the loading
property to display a spinner in the list box indicating that options are being loaded.
type Fruit = { id: number; name: string };
const [loading, setLoading] = useState<boolean>(false);
const [items, setItems] = useState<OptionItem<Fruit>[]>([]);
const [selectedOption, setSelectedOption] = useState<Fruit | undefined>(api.fruits[1]);
async function getFruits() {
await sleep(5000);
return [...api.fruits];
}
function itemize(data: Fruit[]): OptionItem<Fruit>[] {
return data.map((fruit) => ({
title: fruit.name,
value: fruit,
}));
}
useEffect(() => {
let isSubscribed = true;
setLoading(true);
getFruits()
.then((fruits) => isSubscribed && setItems(itemize(fruits)))
.finally(() => isSubscribed && setLoading(false));
return () => {
isSubscribed = false;
};
}, []);
return (
<Select
loading={loading}
items={items}
value={selectedOption}
onChange={setSelectedOption}
compareFn={(a, b) => a?.id === b?.id}
/>
);
You can also use the onSearch
property to fetch new options when the user types in the search bar. You must update the options list with the query results. Be sure to include the selected options in the options list when the search query is empty, otherwise the selection will not be displayed.
type Fruit = { id: number; name: string };
let [items, setItems] = useState<OptionItem<Fruit>[]>([]);
const [selectedOptions, setSelectedOptions] = useState<Fruit[]>([api.fruits[1]]);
const [loading, setLoading] = useState<boolean>(false);
const [query, setQuery] = useState<string>("");
function itemize(data: Fruit[]): OptionItem<typeof selectedOptions>[] {
return data.map((fruit) => ({
title: fruit.name,
value: fruit,
}));
}
function mergeItemsWithSelectedOptions(items: OptionItem<typeof selectedOptions>[]) {
// merge the list of items with selected options, removing duplicates
const selectedItems = itemize(selectedOptions);
return [...items, ...selectedItems].filter(
(item, index, self) => self.findIndex((i) => i.value.id === item.value.id) === index
);
}
function search<T>(items: T[], mapFn: (x: T) => string, query: string, caseSensitive = false) {
const q = caseSensitive ? query : query.toLowerCase();
const map = caseSensitive ? mapFn : (x: T) => mapFn(x).toLowerCase();
return items.filter((item) => map(item).includes(q));
}
async function onSearch(searchQuery: string, caseSensitive: boolean) {
let fetchedItems: OptionItem<typeof selectedOptions>[] = [];
if (searchQuery.length === 0) {
const newItems = itemize(api.fruits.slice(0, 5));
fetchedItems = mergeItemsWithSelectedOptions(newItems);
} else {
await sleep(3000);
const queryedItems = search(api.fruits, (f) => f.name, searchQuery, caseSensitive);
fetchedItems = itemize(queryedItems);
}
setItems(fetchedItems);
setLoading(false);
}
async function onCreate(value: string) {
setLoading(true);
await sleep(3000);
const newFruit = { id: api.fruits.length, name: value };
api.fruits.push(newFruit);
setLoading(false);
return itemize([newFruit])[0];
}
const debouncedSearch = useDebounceFn(onSearch, 500);
useEffect(() => {
debouncedSearch(query, false);
}, [query]);
if (query && loading) {
items = search(items, (i) => i.title, query);
}
return (
<Select
aria-label="List of fruits"
bulkActions
creatable
onChange={setSelectedOptions}
onCreate={onCreate}
onSearch={(q) => {
setLoading(true);
setQuery(q);
}}
loading={loading}
searchHelpText="Start typing to view and select matching items."
items={items}
value={selectedOptions}
/>
);
#Select Overview
Use the showSelectionOverview
property to show a list of selected items just below the Select
button. The user can easily see all selected items in the list box. In addition, the user can easily deselect items directly from the Selection Overview by clicking the X icon.
- Fruits
- Round and usually red or green.
- Vegetables
- Easily recognizable for its long pointy spears.
const items = [
{
heading: "Fruits",
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
},
{
heading: "Vegetables",
items: [
{
title: "Aspargus",
description: "Easily recognizable for its long pointy spears.",
value: "aspargus",
},
{
title: "Carrot",
description: "Orange in color, but purple, black, red, white, and yellow also exist.",
value: "carrot",
},
{
title: "Cocumber",
description: "Usually considered a vegetable, however it's botanically a fruit.",
value: "cocumber",
},
],
},
];
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple", "aspargus"]);
return (
<Select
aria-label="List of fruits and vegetables"
showSelectionOverview
items={items}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);
#Invalid
A Select
can be marked as having an error to indicate that an entered value is invalid. Use the error message to inform the user what has happened, and then provide guidance on next steps or possible solutions. The best practice is to verify the user's data before they have filled in all the fields of a form.
The error message can also indicate that the input is empty, e.g. "Select a country". This can happen if a user clicks the Submit button before they have fully entered the value. However, if we have correctly marked required or optional fields, the user knows which fields are required (see Best Practices). Follow the writing guideline for Issues and issue descriptions.
Some error message to let the user know what's wrong
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<FormElementWrapper
label="Fruit"
name="Fruit"
invalid
error="Some error message to let the user know what's wrong"
>
<Select
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption}
value={selectedOption}
/>
</FormElementWrapper>
);
#Disabled
The disabled state indicates that the Select
exists but is not available under some circumstances. This can be used to maintain continuity of the layout and to communicate that it may be available later. If possible, provide a hint text or a visual clue to explain why the Select
is disabled to avoid user confusion.
const [selectedOption1, setSelectedOption1] = useState<string | undefined>();
const [selectedOption2, setSelectedOption2] = useState<string[]>(["apple", "banana"]);
return (
<>
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption1}
value={selectedOption1}
disabled
/>
<br />
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption2}
value={selectedOption2}
showSelectionOverview
disabled
/>
</>
);
#Custom option element
If the item variants do not meet your requirements, you can use custom option elements. Consistency is critical when using custom option elements. Make sure they have a consistent pattern and contain only the necessary information. This example shows the consistency of labels with basic checkboxes. Note that you can provide custom renderers both for the options in the list box and for the selected items in the selection overview.
- Remove Apple from cart
- Remove Banana from cart
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple", "banana"]);
return (
<Select
aria-label="List of fruits"
listboxHeading="Shopping list"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
optionRenderer={(props) => {
const { item, onSelect, isSelected } = props;
return (
<BaseSelectOption key={item.title} style={{ boxShadow: "none" }} {...props}>
<Checkbox
tabIndex={-1}
disabled={item.disabled}
onChange={() => onSelect(item)}
checked={isSelected}
value={item.title}
>
{item.title}
</Checkbox>
</BaseSelectOption>
);
}}
showSelectionOverview
overviewOptionRenderer={(props) => {
const { item } = props;
return (
<BaseOverviewOption key={item.title} {...props}>
<InlineText>
Remove <strong>{item.title}</strong> from cart
</InlineText>
</BaseOverviewOption>
);
}}
/>
);
#Custom option title
You can also use a simpler way to create custom option elements. This example shows the use of the useOptionTitleRenderer
hook to customize the way the option title is rendered.
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple"]);
const customRenderers = useOptionTitleRenderer<string[]>((title, opt) => (
<Pill variant={{ type: "static" }} size={opt.isOverview ? "medium" : "small"}>
<InlineText tone={opt.disabled ? "subtle" : "neutralDark"}>
<TextHighlight value={title} needle={opt.needle} caseSensitive={opt.caseSensitive} />
</InlineText>
</Pill>
));
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry", disabled: true },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
showSelectionOverview
searchable="always"
{...customRenderers}
/>
);
#Custom button element
If the default button does not meet your requirements, you can use a custom button element. Consistency is critical when using a custom button element. Make sure it contain only the necessary information. This example shows a label with description.
const [selectedOption, setSelectedOption] = useState<string | undefined>("blueberry");
return (
<Select
aria-label="List of fruits"
buttonRenderer={(option?: OptionItem<string>) =>
option ? (
<BigSmall
style={{ textAlign: "start", maxWidth: "80%" }}
big={option.title}
small={
<span
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
display: "block",
}}
>
{option.description}
</span>
}
/>
) : (
"Select a fruit"
)
}
buttonProps={{ size: "large" }}
items={[
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);
#Custom size
Choose an appropriate width
The items should not be wider than the text. The text may be truncated if it's wider than the item, making it hard for users to read.
Custom size support provides more flexibility in structuring layouts and should be used to create a hierarchy of importance within the page. Use a consistent width
when used alongside other Form Element Wrapper on the same page. Use the fullWidth
size sparingly.
To control the width of the button, you can use
- the
width
property to adjust the width of the button. - the
fullWidth
property to fully extend the width of the button.
By default, the list box has a minimum width of 15 rem, and a maximum width of 37.5 rem. If you want to control the width of the list box, it is recommend to use
- the
listboxWidth
property to adjust the width of the list box. - the
noListboxMinWidth
property to disable the default minimum list box width. - the
noListboxMaxWidth
property to disable the default maximum list box width.
Adjust height for list box
By default, the list box has a maximum height of 20.25 rem, which is about 7.5 items in the list. This means that 7 items will completely fit in the list box and the 8th item will be cut in half, which alongside the scrollbar, visually indicates that there are more items in the list box.
When using custom option elements, the number of items displayed can vary, but make sure the user can clearly see these indicators of list box continuity, especially in a Modal
.
Fixed width
Fixed width for button and list box
Full width
Full width with selection overview
- Round and usually red or green.
- It’s a berry and it’s blue.
const [selectedOption, setSelectedOption] = useState<string[]>(["apple", "blueberry"]);
const commomProps = {
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
searchable: "always" as const,
bulkActions: true,
value: selectedOption,
onChange: setSelectedOption,
ariaLabel: "List of fruits",
};
return (
<>
<Paragraph>Fixed width</Paragraph>
<Select {...commomProps} width={600} />
<br /> <br />
<Paragraph>Fixed width for button and list box</Paragraph>
<Select {...commomProps} width={600} listboxWidth={600} />
<br /> <br />
<Paragraph>Full width</Paragraph>
<Select {...commomProps} fullWidth />
<br /> <br />
<Paragraph>Full width with selection overview</Paragraph>
<Select {...commomProps} fullWidth showSelectionOverview />
</>
);
#List box placement
Sometimes, the list box may not be visible because it is placed outside the viewport. You can use the placement
property to specify where the list box should be placed. The default value is bottom-start
.
const [showModal, setShowModal] = useState(false);
const [selectedOptions, setSelectedOptions] = useState<number[]>([]);
return (
<>
<Button onClick={() => setShowModal(true)}>Trigger modal</Button>
<Modal shown={showModal} headerTitle="Placement Example" onClose={() => setShowModal(false)}>
<Modal.Content>
<FormElementWrapper label="Fruit" name="Fruit">
<Select
aria-label="List of fruits"
bulkActions
placement="auto-start"
items={[
{ value: 0, title: "Apple" },
{ value: 1, title: "Banana" },
{ value: 2, title: "Blueberry" },
{ value: 3, title: "Cherry" },
{ value: 4, title: "Grape" },
{ value: 5, title: "Guava" },
{ value: 6, title: "Lemon" },
{ value: 7, title: "Lime" },
{ value: 8, title: "Orange" },
{ value: 9, title: "Peach" },
{ value: 10, title: "Pear" },
{ value: 11, title: "Pineapple" },
{ value: 12, title: "Raspberry" },
{ value: 13, title: "Strawberry" },
{ value: 14, title: "Watermelon" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
</FormElementWrapper>
</Modal.Content>
</Modal>
</>
);
#Usage with data-observe-keys
Use data-observe-key
on the main component and/or individual items to create identifiers, which are useful for tracking user interactivity, for example. Inner buttons and inputs, such as confirm, cancel, clear, bulk actions, and search bar, will be assigned with the same data-observe-key
plus a discriminator. For instance, when using <Select data-observe-key="foo" />
, the clear button will be assigned with foo-ClearButton
. See below a complete example and note this pattern in action.
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [selectedOptions, setSelectedOptions] = useState<string[]>(["banana"]);
const itemize = (value: string): OptionItem<string> => ({
title: value,
value: value.toLowerCase(),
"data-observe-key": `${value}Item`,
});
return (
<Select
bulkActions
showSelectionOverview
data-observe-key="FruitSelect"
aria-label="List of fruits"
items={items.map(itemize)}
value={selectedOptions}
onChange={setSelectedOptions}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
return itemize(newValue);
}}
/>
);
#Indeterminate State
The useSelectIndeterminateState
hook enhances the Select component by providing a subset of props with a custom state manager. This enables the creation of an intermediate selection state. Let's take a look at an example that demonstrates how to utilize this hook to introduce a new type of selection: partial selection. In this scenario, each option represents a tag, and each tag can be active on zero or multiple websites. A tag is considered fully selected if it is active on all websites. If it is active on some, but not all websites, it is classified as partially selected. Finally, if a tag is not active on any website, it is considered unselected.
- 2 of 6 sites are tagged
- 4 of 6 sites are tagged
- 6 of 6 sites are tagged
type Tag = { name: string; sitesCount: number };
// maximum number of sites
const totalOfSites = 6;
// this state simulates the tags list from your API
const [tagsFromAPI, setTagsFromAPI] = useState<Tag[]>([
{ name: "Tag 1", sitesCount: 0 },
{ name: "Tag 2", sitesCount: 2 },
{ name: "Tag 3", sitesCount: 4 },
{ name: "Tag 4", sitesCount: 6 },
]);
// local copy of the tags list that will be updated with new sites count
const [tags, setTags] = useState<Tag[]>(tagsFromAPI);
const [selectedTags, setSelectedTags] = useState<Tag[]>(tags.filter((tag) => tag.sitesCount > 0));
const onChange = (newSelection: Tag[], updatedTags: Tag[]) => {
// update the local state
setSelectedTags(newSelection);
// submit the changes to your API to update the tag sites count
console.log("Call your API to update all tag sites count", updatedTags);
setTagsFromAPI(updatedTags);
};
const compareFn = (a: Tag, b: Tag) => a.name === b.name;
const itemize = (tag: Tag): OptionItem<Tag> => ({
title: tag.name,
value: tag,
description: `${tag.sitesCount} of ${totalOfSites} sites are tagged`,
});
const indeterminateStateProps = useSelectIndeterminateState<Tag[]>(
tagsFromAPI,
[tags, setTags],
"sitesCount",
totalOfSites,
onChange,
compareFn
);
return (
<Select
{...indeterminateStateProps}
aria-label="List of tags on site"
items={tags.map(itemize)}
value={selectedTags}
showSelectedItemsOnTop={false}
showSelectionOverview
bulkActions
creatable
onCreate={(value: string) => {
const newTag = { name: value, sitesCount: totalOfSites };
setTags([...tags, newTag]);
return itemize(newTag);
}}
/>
);
#Properties
Property | Description | Defined | Value |
---|---|---|---|
valueRequired | unknown Value of the form control | ||
onChangeRequired | function Callback for onChange event | ||
nameOptional | string Name applied to the form control | ||
idOptional | string Id applied to the form control | ||
invalidOptional | boolean Is the form control invalid | ||
onBlurOptional | function Callback for onBlur event | ||
aria-labelOptional | string Label of the form control | ||
aria-describedbyOptional | string ID of an an element that describes what the form control is for | ||
aria-labelledbyOptional | string ID of an an element that labels this form control | ||
itemsOptional | object[] List of options or groups of options | ||
compareFnOptional | function Function that return true is two items are equal | ||
placeholderOptional | string Placeholder for the select | ||
iconOnlyOptional | boolean Popover anchor becomes an icon-only button | ||
fullWidthOptional | boolean Should the select be full width? | ||
defaultOptionOptional | unknown Option selected by default. If undefined, the default is the first option. Use noDefaultOption property to disable it. | ||
noDefaultOptionOptional | boolean If true, does not use the default option. | ||
disabledOptional | boolean Can the select button be clicked | ||
loadingOptional | boolean Displays a spinner in the listbox | ||
searchableOptional | "always" | "auto" | "never" Enables searching functionality. If set to auto, search bar is visible only when there are more than 7 items. | ||
onSearchOptional | function Handler for searching event | ||
caseSensitiveOptional | boolean Enables case sensitive search. | ||
searchPlaceholderOptional | string Placeholder for search field | ||
searchHelpTextOptional | element Placeholder for search field | ||
searchLabelOptional | string Aria-label for search field | ||
searchableContentOptional | function Defines which text content the search should be applied to. Defaults to the options title. | ||
bulkActionsOptional | boolean Enables bulk actions such as select/deselect all | ||
creatableOptional | boolean Enables creating options when the search doesn't exactly match any options | ||
onCreateOptional | function Callback for onCreate option event | ||
createButtonLabelOptional | string Label to be displayed on the create option button | ||
hideClearButtonOptional | boolean Hides the button to clear the selection | ||
showSelectedItemsOnTopOptional | boolean If enabled, shows selected items on top when opening the listbox. In multi-selection mode, it defaults to true. | ||
showSelectionOverviewOptional | boolean Shows selected items in a box bellow the select input | ||
overviewLabelOptional | string Label that describes the overview list | ||
roleOptional | "listbox" | "menu" Role of the list box | ||
keyboardTypingIntervalOptional | number Amount of time in milliseconds used to identify that the user has finished typing a string | ||
maxNumberOfItemsOptional | number Maximum number of items that can be selected | ||
optionRendererOptional | function Custom renderer for options | ||
overviewOptionRendererOptional | function Custom renderer for overview options | ||
object props to pass down to the button | |||
object Ref of the button | |||
widthOptional | number Controls the width of the select button. | ||
listboxWidthOptional | number Controls the width of the select listbox. | ||
noListboxMinWidthOptional | boolean If true, the default listbox minimum width won't be set | ||
noListboxMaxWidthOptional | boolean If true, the default listbox maximum width won't be set | ||
placementOptional | "auto" | "auto-end" | "auto-start" | "bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start" Preferred placement for the listbox | ||
allowedAutoPlacementsOptional | literal-union[] Allowed placements for the listbox when using an "auto" value for the "placement" prop | ||
hideChevronOptional | boolean Hide the chevron icon | ||
function Defines how the selected item is rendered in the button. Defaults to the option title. | |||
listboxHeadingOptional | element Heading for the listbox | ||
stateManagerBuilderOptional | function Custom function to handle selection changes, where the input is a list of options affected and the output is a list of selected options | ||
data-observe-keyOptional | string Unique string, used by external script e.g. for event tracking | ||
classNameOptional | string Custom className that's applied to the outermost element (only intended for special cases) | ||
styleOptional | object Style object to apply custom inline styles (only intended for special cases) | ||
tabIndexOptional | number Tab index of the outermost HTML element of the component | ||
onKeyDownOptional | function Callback for onKeyDown event | ||
onMouseDownOptional | function Callback for onMouseDown event | ||
onMouseEnterOptional | function Callback for onMouseEnter event | ||
onMouseLeaveOptional | function Callback for onMouseLeave event | ||
onFocusOptional | function Callback for onFocus event |
#Guidelines
#Best practices
#Do not use when
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications
#Writing
#Notable Changes
#Version 52.0.0
The multiple
prop and both SingleSelectProps
and MultiSelectProps
types were removed in favor of a single SelectProps
type. With this change, the type inference logic has been improved to determine the mode of the Select
based on the value
prop's type. If the value
prop is an array, then the multi select mode is enabled, otherwise, the single select mode takes place. These changes streamline the codebase and make it easier to use the Select
component.
Therefore, to adapt to these changes, it is necessary to remove the multiple
prop and explicit literals in the React code and use the new common SelectProps
type.
#Before
const [value, setValue] = useState<string[]>([]);
<Select<string>
multiple
value={value}
onChange={setValue}
/>
#After
const [value, setValue] = useState<string[]>([]);
<Select
value={value}
onChange={setValue}
/>
#Version 60.0.0
The onCreate
function must now return an OptionItem
or a boolean
. Also, developers don't need to include the newly created option in the list of selected options, as the component will automatically select it.
#Before
const [items, setItems] = useState<string[]>(["Foo", "Bar"]);
const [selectedOption, setSelectedOption] = useState<string>();
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
setSelectedOption(newValue);
}}
/>
#After
const [items, setItems] = useState<string[]>(["Foo", "Bar"]);
const [selectedOption, setSelectedOption] = useState<string>();
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
return itemize(newValue);
}}
/>