@biem/ui

Select

A field that lets users choose one or several options from a list that opens in a popup.

A select lets users choose one option from a list that opens in a popup, or several with multiple. The trigger shows the selected option, or a placeholder while none is selected, and a press on it opens the list. By default, the popup opens over the trigger, so that the selected option lines up with the text in the trigger. items on Select gives the select the label of each value, so the trigger shows the label of the selected option instead of its raw value. SelectGroup provides the padding around the options, so the options sit in a SelectGroup even when the list has a single group. The trigger is as wide as its content, and the popup takes the width of the trigger but is at least 9rem wide. The trigger has no visible label of its own, so the examples name it with aria-label.

Usage

select-default.tsx
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@biem/ui/components/ui/select";

const stores = [
	{ label: "Basel", value: "basel" },
	{ label: "Bern", value: "bern" },
	{ label: "Geneva", value: "geneva" },
	{ label: "Lucerne", value: "lucerne" },
	{ label: "Zurich", value: "zurich" },
];

export function SelectDefault() {
	return (
		<Select items={stores}>
			<SelectTrigger aria-label="Store">
				<SelectValue placeholder="Select a store"/>
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					{stores.map((store) => (
						<SelectItem key={store.value} value={store.value}>{store.label}</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Examples

With Groups

Long lists often combine options of different kinds, for example product categories from several departments. Related options sit together in a SelectGroup, and a SelectLabel names each group, so users find an option faster and assistive technology reads the name of its group as well.

With Separator

Labels alone may not keep groups apart clearly enough, especially in a long list that users scan quickly. Placed between two groups, SelectSeparator marks where one set of options ends and the next begins, so users recognize the change of kind without reading every label.

With Icon

In a dense form, users should recognize what a field asks for before they read its value. An icon in the trigger gives that hint at a glance and stays in place whichever option users select.

Small

Toolbars, filter bars and table headers leave little room for a field. With size set to "sm" on SelectTrigger, the select claims less space and fits into such a row without outweighing the content it filters.

Default Size

Forms, dialogs and settings pages give a field the room it needs. There the trigger keeps the size "default", which applies whenever no other size is set, so a select lines up with the inputs around it.

Scrollable

Certain choices span more options than the screen can show at once, such as every supplier in an assortment. The list then stays within the available space and scrolls. Wherever further options lie beyond the visible ones, a button at that end scrolls on while the pointer rests on it, and users can also type the first characters of an option to move straight to it.

Multiple

Reports and filters often cover more than one option at a time, such as several regions in a sales comparison. Through multiple, users select as many options as they need, the popup stays open between their choices, and another press on a selected option removes it again.

Without Item Alignment

Opening over the trigger keeps the selected option where users were looking, yet it covers the field itself. With alignItemWithTrigger set to false on SelectContent, the popup opens beside the trigger instead, below it unless side says otherwise, so the field and its surroundings stay in view while users choose.

Top Side

At the bottom of a panel or in a footer bar, the room for a list lies above the select. Set to "top", side opens the popup there once alignItemWithTrigger is false, because a popup that lies over the trigger ignores the side.

Right Side

Along the left edge of a screen, in a sidebar for instance, the open space lies to the right of the select. Together with alignItemWithTrigger set to false, side set to "right" lets the list unfold into that space instead of covering what lies below the trigger.

Left Side

Panels on the right edge of a screen, such as a details pane, leave their room to the left. Once alignItemWithTrigger is false, side set to "left" opens the popup on that side, while the values "inline-start" and "inline-end" follow the reading direction for products in right-to-left languages.

Default Value

Many forms begin with a choice that most users keep, such as the current week in a sales report. Through defaultValue the select starts with that option selected, and users change it only when they need another one.

Controlled Value

Other parts of the interface often depend on the selection, such as a dashboard that filters its figures by the chosen category. The application then holds the value through value and onValueChange, so it can reset or change the selection from elsewhere while the select always shows the current choice.

Controlled Open State

Another part of the interface may need to open the list, for example a keyboard shortcut or a guided step that points users to a missing choice. Through open and onOpenChange, the application holds whether the popup is open, while users still open and close it as usual.

Disabled

Until an earlier choice is made, a select may have nothing meaningful to offer, such as sizes before a product is chosen. Marked as disabled, it neither opens nor changes its value, keyboard focus passes over it, and the form leaves its value out.

Disabled Item

Single options are sometimes unavailable for a while, such as a promotion that has not started yet. With disabled on its SelectItem, the option remains in the list, so users still see the full range, but they cannot select it.

Read-Only

Some values are decided elsewhere, by head office or by an automated process, yet users still need to know them. Through readOnly, users open the list and read every option without changing the selection, and the form still submits the value.

Invalid

A required choice that is missing, or one that no longer fits the rest of a form, should stand out before users submit. With aria-invalid on SelectTrigger, the field signals the error and assistive technology announces it, so users find the field that needs their attention.

API Reference

Select

Propitems
TypeRecord<string, ReactNode> | ReadonlyArray<{ label: ReactNode; value: any }> | ReadonlyArray<Group<any>>
Default–
DescriptionGives the select the label of each value, so that SelectValue shows the label of the selected option instead of its raw value. It takes an object that maps each value to its label, an array of objects with label and value, or an array of groups that each hold such objects in items. It doesn't render the options: each one still needs a SelectItem.
Propvalue
TypeValue | Value[] | null
Default–
DescriptionSets the selected value and makes the select controlled. Update it in onValueChange. With multiple, it is an array of values.
PropdefaultValue
TypeValue | Value[] | null
Default–
DescriptionSets the value that is selected at first render. The select then tracks the selection itself. With multiple, it is an array of values.
ProponValueChange
Type(value: Value | Value[] | null, eventDetails: SelectRootChangeEventDetails) => void
Default–
DescriptionRuns when users select an option, and with multiple also when they deselect one. It receives the value that follows: the value of the option, or with multiple the array of selected values. When the selected option is removed from the list, a select with a single value returns to the value of the first render, or to null if that option is gone as well.
Propmultiple
Typeboolean
Defaultfalse
DescriptionLets users select several options. The value is then an array, a press on a selected option removes it from the selection, and the popup stays open after each press.
Propopen
Typeboolean
Default–
DescriptionSets whether the popup is open and makes the open state controlled. Update it in onOpenChange.
PropdefaultOpen
Typeboolean
Defaultfalse
DescriptionOpens the popup at first render. The select then tracks its open state itself.
ProponOpenChange
Type(open: boolean, eventDetails: SelectRootChangeEventDetails) => void
Default–
DescriptionRuns when users open or close the popup. It receives the open state that follows.
ProponOpenChangeComplete
Type(open: boolean) => void
Default–
DescriptionRuns after the popup has opened or closed and its animations have finished.
Propmodal
Typeboolean
Defaulttrue
DescriptionLocks the page while the popup is open: the page doesn't scroll, and elements outside of the select don't react to the pointer. With false, elements outside of the select react to the pointer. The page still doesn't scroll while the popup lies over the trigger, which alignItemWithTrigger of SelectContent decides. When a touch opened a modal select, the page still scrolls unless the popup is about as wide as the viewport.
ProphighlightItemOnHover
Typeboolean
Defaulttrue
DescriptionMoves the highlight to the option under the pointer. With false, only the keyboard moves the highlight.
PropitemToStringLabel
Type(itemValue: Value) => string
Default–
DescriptionConverts a value that is an object to the text that the trigger shows. An object with the properties value and label doesn't need it, because the select reads label itself.
PropitemToStringValue
Type(itemValue: Value) => string
Default–
DescriptionConverts a value that is an object to the text that the form submits. An object with the properties value and label doesn't need it, because the select reads value itself.
PropisItemEqualToValue
Type(itemValue: Value, value: Value) => boolean
Default–
DescriptionDecides whether the value of an option equals the selected value. Set it when values are objects that aren't the same instances as the selected value. Without it, the select compares with Object.is.
Propdisabled
Typeboolean
Defaultfalse
DescriptionPrevents users from opening the popup and changing the value, and leaves the value out of the submitted form. It disables the trigger and every option.
PropreadOnly
Typeboolean
Defaultfalse
DescriptionPrevents users from changing the value. The popup still opens, so users can read the options, and the value is submitted with the form.
Proprequired
Typeboolean
Defaultfalse
DescriptionRequires users to select an option before they can submit the form.
Propname
Typestring
Default–
DescriptionIdentifies the select when the form is submitted. With multiple, the form submits one entry with this name for each selected value.
Propform
Typestring
Default–
DescriptionSets the ID of the form that the select belongs to. Set it when the select is rendered outside of that form.
PropautoComplete
Typestring
Default–
DescriptionGives the browser a hint for autofill, as the HTML attribute autocomplete does.
Propid
Typestring
Default–
DescriptionSets the ID of the trigger, so that a label can refer to the select with htmlFor.
PropinputRef
TypeRef<HTMLInputElement>
Default–
DescriptionReceives the hidden input element that carries the value of the select in a form.
PropactionsRef
TypeRefObject<SelectRootActions | null>
Default–
DescriptionReceives the function unmount, which removes the popup from the DOM after a closing animation that the code controls itself.

SelectTrigger

Propsize
Type"sm" | "default"
Default"default"
DescriptionSets the height of the trigger: 2rem for "default" and 1.75rem for "sm". "sm" also reduces the corner radius.
Propdisabled
Typeboolean
Defaultfalse
DescriptionPrevents users from opening the popup with this trigger. A disabled select disables the trigger as well.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the trigger with another component.
PropnativeButton
Typeboolean
Defaulttrue
DescriptionDeclares whether the rendered element is a native button. Set it to false when render returns a different element.

SelectValue

Propplaceholder
TypeReactNode
Default–
DescriptionSets the content that the part shows while no option is selected. children takes precedence over it, and so does the label that items gives to the value null.
Propchildren
TypeReactNode | ((value: any) => ReactNode)
Default–
DescriptionReplaces the label of the selected option, which the part shows without it. A function receives the selected value, or with multiple the array of selected values, and returns the content.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the value with another component.

SelectContent

PropalignItemWithTrigger
Typeboolean
Defaulttrue
DescriptionOpens the popup over the trigger, so that the selected option lines up with the text in the trigger. While it applies, side, sideOffset, align and alignOffset have no effect. It doesn't apply when a touch opened the select, and it gives way to the side of the trigger when the viewport leaves too little space around the trigger. With false, the popup always opens on a side of the trigger.
Propside
Type"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"
Default"bottom"
DescriptionSets the side of the trigger that the popup opens on. "inline-start" and "inline-end" follow the reading direction. The popup moves to the opposite side when the chosen one has too little space.
PropsideOffset
Typenumber | OffsetFunction
Default4
DescriptionSets the distance between the trigger and the popup in pixels. A function receives the side, the alignment and the dimensions of the trigger and the popup, and returns the distance.
Propalign
Type"start" | "center" | "end"
Default"center"
DescriptionSets where the popup lines up with the trigger along the chosen side: at its start, its center or its end.
PropalignOffset
Typenumber | OffsetFunction
Default0
DescriptionMoves the popup along the alignment axis by a number of pixels. A function receives the same data as the one for sideOffset and returns the offset.
PropfinalFocus
Typeboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void)
Default–
DescriptionDetermines the element that receives focus when the select closes. Without it, focus returns to the trigger or to the element that had focus before.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the content with another component.

SelectGroup

Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the group with another component.

SelectLabel

Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the label with another component.

SelectItem

Propvalue
Typeany
Defaultnull
DescriptionIdentifies the option. value, defaultValue and onValueChange of the select work with it.
Propdisabled
Typeboolean
Defaultfalse
DescriptionPrevents users from selecting the option.
Proplabel
Typestring
Default–
DescriptionSets the text that the select matches when users type to move to an option. Without it, the select matches the text content of the option.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the item with another component.
PropnativeButton
Typeboolean
Defaultfalse
DescriptionDeclares whether the rendered element is a native button. Set it to true when render returns a button.

SelectSeparator

Proporientation
Type"horizontal" | "vertical"
Default"horizontal"
DescriptionSets the data-orientation attribute of the separator. The separator draws a horizontal line with either value.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the separator with another component.

SelectScrollUpButton

PropkeepMounted
Typeboolean
Defaultfalse
DescriptionKeeps the button in the DOM while the list can't scroll up. Without it, the select renders the button only while options lie above the visible ones. SelectContent renders this button itself at the top of the list.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the button with another component.

SelectScrollDownButton

PropkeepMounted
Typeboolean
Defaultfalse
DescriptionKeeps the button in the DOM while the list can't scroll down. Without it, the select renders the button only while options lie below the visible ones. SelectContent renders this button itself at the bottom of the list.
Proprender
TypeReactElement | render function
Default–
DescriptionReplaces the native element or composes the button with another component.