Select
The MD3 exposed dropdown menu: a text field that opens a list of options to pick one value from. The field looks exactly like a TextField (filled or outlined). The list opens in the top layer below the field, flips above it when there is no room, and is exactly as wide as the field.
Variants
- Apple
- Banana
- Cherry
- Apple
- Banana
- Cherry
<Select variant="outlined" label="Fruit" defaultValue="banana">
<Option value="apple">Apple</Option>
<Option value="banana">Banana</Option>
<Option value="cherry">Cherry</Option>
</Select>
Each Option has a string value. Its children are the label, and the field
shows that label once the option is selected. The list uses the m3
"Text field - Select" menu tokens: a surface-container surface with 4dp
corners and 48dp label-large options, and the selected option is shown with
the surface-container-highest container. Those tokens have no check mark or
selected label color, so there is none: the field itself shows the selected
value, and assistive technology hears it through aria-selected.
Tonal style (experimental)
tonal gives the field the same Expressive tonal look as
TextField tonal: a 12dp
rounded container, no indicator line on filled, and an outline-variant
border with the label inside the container on outlined. Compose still
marks these values TODO(b/448727879), so the look may change once the spec
settles.
- Apple
- Banana
- Apple
- Banana
Controlled value
value + onChange(event, value) control the selection. onChange fires
only when the user picks a different option. Use defaultValue for an
uncontrolled Select. '' means nothing is selected; placeholder is shown
then, once the label has floated.
- Small
- Medium
- Large
- Extra large (sold out)
const [size, setSize] = useState('')
<Select label="Size" placeholder="Choose a size" value={size} onChange={(event, value) => setSize(value)}>
<Option value="s">Small</Option>
<Option value="m">Medium</Option>
<Option value="xl" disabled>Extra large (sold out)</Option>
</Select>
disabled options are shown but can't be picked, and keyboard navigation
skips them. The open state can be controlled too, with open /
defaultOpen / onOpenChange.
Icons
startIcon on the Select puts a decorative icon at the start of the field.
startIcon on an Option puts a 24dp icon at the start of the option. It
stays while that option is selected.
- Flight
- Car
- Train
- Bike
<Select label="Travel mode" startIcon={<CommuteIcon />} defaultValue="train">
<Option value="flight" startIcon={<FlightIcon />}>Flight</Option>
<Option value="train" startIcon={<TrainIcon />}>Train</Option>
</Select>
Option supporting text
supportingText on an Option adds a second line below the label (m3
menu-item supporting text: body-small, on-surface-variant). Both lines wrap
instead of being cut off, and the list keeps the width of the field: the
112–280dp width of a menu item is a menu rule, not a select rule. The
option's accessible name is its label, and the supporting text is its
description (aria-describedby). The field shows only the label once the
option is picked, and typeahead matches only the label.
- FreeFor one person
- TeamShared projects, roles and a single bill for everyone
- EnterpriseSSO and audit logs
<Select label="Plan" defaultValue="team">
<Option value="free" supportingText="For one person">Free</Option>
<Option value="team" supportingText="Shared projects, roles and a single bill">Team</Option>
</Select>
Label placement
labelPlacement and shrinkLabel work as they do on
TextField: labelPlacement="above" puts the label
above the container, always minimized, and shrinkLabel keeps the floating
label minimized and shows the placeholder while nothing is selected. The
list still opens right below the container. m3 doesn't describe an above
label for dropdowns. This is the Compose TextFieldLabelPosition mapping
from TextField, applied to the field that anchors the menu.
- Apple
- Banana
- Apple
- Banana
<Select label="Fruit" labelPlacement="above" placeholder="Pick one">…</Select>
<Select label="Fruit" variant="outlined" shrinkLabel placeholder="Pick one">…</Select>
Open on focus
By default the list opens on a click or a key (Enter, Space, the arrow
keys, typing), never just because the field got focus. Neither m3 nor
Compose opens a select on focus. openOnFocus opts in, for forms that also
use Autocomplete openOnFocus: the
list opens on the selected option when focus enters the field.
- Apple
- Banana
<Select label="Fruit" openOnFocus>…</Select>
Supporting text, error & states
- Japan
- United States
- Japan
- United States
- Japan
- United States
error switches to the error colors and sets aria-invalid. errorText
replaces supportingText while in error and is announced (role="alert").
label, supportingText and errorText also accept React content (e.g. a
short link in the supporting text), with the same guidance as
TextField: a short, single-line
label without links. fullWidth stretches the field, and with it the list, to the container's
width.
Forms
The value is carried by a visually hidden native <input>. With name it is
submitted with its <form> (or with the form that form="id" points to).
required takes part in native validation, and a form reset puts an
uncontrolled Select back to its defaultValue. A disabled Select is not
submitted. inputRef gives you that native input.
<form onSubmit={handleSubmit}>
<Select label="Plan" name="plan" required defaultValue="pro">
<Option value="free">Free</Option>
<Option value="pro">Pro</Option>
</Select>
<Button type="submit">Submit</Button>
<Button type="reset" variant="outlined">Reset</Button>
</form>
Native select
With native, the same field (label, filled / outlined / tonal, start icon,
supporting / error text, required *, disabled, the trailing arrow) is drawn
around a real <select>, and the popup is the operating system's picker —
handy for plain forms and on mobile.
- Children are native
<option>/<optgroup>elements, notOption(TypeScript picks the native props whennativeis set). refpoints at the field's root (as everywhere else) andinputRefat the<select>.className/stylego to the root; every other attribute goes to the<select>.onChange(event, value):eventis the nativeChangeEvent<HTMLSelectElement>, soevent.target.valueworks too.- Forms:
name,form,required,disabled,value/defaultValueand form reset behave exactly as on a plain<select>. A controlled value survives a reset. - The label floats while the selected value is not empty (or while
focused). An empty-value option, such as
<option value="" />, counts as "nothing selected": the label rests in the field, and that option's text shows like a placeholder only while focused. - There is no listbox, no
Optionicons, noopen/placeholderprops, and nomultiple. The arrow does not rotate, because the page cannot tell when the OS picker is open.
<Select
native
label="Fruit"
name="fruit"
value={fruit}
onChange={(event, value) => setFruit(value)}
>
<option value="" />
<option value="apple">Apple</option>
<optgroup label="Berries">
<option value="blueberry">Blueberry</option>
</optgroup>
</Select>
Spec note: m3.material.io and Jetpack Compose have no native select (Compose's
exposed dropdown always draws its own menu), so this is a web adaptation in the
manner of MUI's Select native (ruling B53). The field uses the m3 select text
field tokens; the menu tokens do not apply because the OS draws the popup.
There are no sizes — m3 has no text-field sizes.
Accessibility
Follows the WAI-ARIA APG select-only combobox pattern:
- The field is a focusable
role="combobox"named by thelabel(or byaria-label/aria-labelledby), witharia-expanded,aria-controls, andaria-describedbypointing at the supporting text. The popup is arole="listbox"ofoptions witharia-selected. - Focus stays on the combobox while the list is open. The active option is
exposed through
aria-activedescendantand shown with a focus ring. - Closed: ↓ / ↑ / Enter / Space open on the selected option, Home / End open on the first / last option, and typing opens on the first option that matches.
- Open: ↓ / ↑ move (without wrapping), Home / End / PageUp / PageDown jump, and typing moves by typeahead. Enter / Space select and close, and Tab selects and moves on. Escape closes without selecting.
- Disabled options are announced (
aria-disabled) but skipped by the keyboard.
Props
Select
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Native <option> / <optgroup> elements. |
className | string | — | Applied to the field's root element. |
defaultValue | string | | Uncontrolled initial value (default: the first enabled option, as natively). |
error | boolean | false | Error state — error colors, aria-invalid, and errorText. Ignored while disabled. |
errorText | ReactNode | — | Shown instead of supportingText (with role="alert") when error is set. |
fullWidth | boolean | false | Stretch the field to the container's width. |
inputRef | Ref<HTMLSelectElement> | — | Ref to the native <select> (the forwarded ref points at the root). |
label | ReactNode | — | Floating label (a <label> for the <select>). Rendered with a trailing
* when required. Floats while focused or while the selected value is
not empty. |
native * | true | — | Render a native <select> with the OS picker instead of the listbox.
Children are then <option> / <optgroup> elements. |
onChange | ((event: ChangeEvent<HTMLSelectElement, Element>, value: string) => void) | — | Fires on the native change event — with that event (so
event.target.value works, as in MUI) and the new value (A2). Not fired
on form reset. |
startIcon | ReactNode | — | Icon at the start of the field (decorative). |
style | CSSProperties | — | Applied to the field's root element. |
supportingText | ReactNode | — | Helper line below the field. Replaced by errorText while in error. |
tonal | boolean | false | Expressive tonal style, as TextField tonal.
@experimental Compose still marks these values TODO(b/448727879). |
value | string | — | Controlled selected value. |
variant | "filled" | "outlined" | filled | Text field container style. |
Option
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The option's label. Also what the field shows once it is selected. |
disabled | boolean | false | Visible, inert, and skipped by keyboard navigation. |
startIcon | ReactNode | — | Icon at the start of the option (decorative, 24dp). |
supportingText | ReactNode | — | A second line below the label (m3 menu-item supporting text: body-small,
on-surface-variant). Both lines wrap. Exposed as the option's
description (aria-describedby) — the accessible name stays the label,
and only the label is shown in the field, matched by typeahead, and
matched by Autocomplete filtering. |
textValue | string | — | Plain-text label for an Autocomplete: the text the input shows once the
option is picked and the text filtering matches. Defaults to the text
content of children. Ignored by Select. |
value * | string | — | The value this option selects (submitted with the form). |