Skip to main content

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​

Filled
  • Apple
  • Banana
  • Cherry
Outlined
  • 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.

Filled tonal
  • Apple
  • Banana
Outlined tonal
  • 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.

Size
  • Small
  • Medium
  • Large
  • Extra large (sold out)
Value: —
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.

Travel mode
  • 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.

Plan
  • 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.

Fruit
  • Apple
  • Banana
Fruit
  • 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.

Fruit
  • Apple
  • Banana
<Select label="Fruit" openOnFocus>…</Select>

Supporting text, error & states​

Country
Used for shipping
  • Japan
  • United States
Country
Choose a country
  • Japan
  • United States
Country
  • 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.

Plan
  • Free
  • Pro
  • Team
Submitted: —
<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, not Option (TypeScript picks the native props when native is set).
  • ref points at the field's root (as everywhere else) and inputRef at the <select>. className / style go to the root; every other attribute goes to the <select>.
  • onChange(event, value): event is the native ChangeEvent<HTMLSelectElement>, so event.target.value works too.
  • Forms: name, form, required, disabled, value / defaultValue and 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 Option icons, no open / placeholder props, and no multiple. The arrow does not rotate, because the page cannot tell when the OS picker is open.
Billed monthly
<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 the label (or by aria-label / aria-labelledby), with aria-expanded, aria-controls, and aria-describedby pointing at the supporting text. The popup is a role="listbox" of options with aria-selected.
  • Focus stays on the combobox while the list is open. The active option is exposed through aria-activedescendant and 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​

PropTypeDefaultDescription
childrenReactNode—Native <option> / <optgroup> elements.
classNamestring—Applied to the field's root element.
defaultValuestringUncontrolled initial value (default: the first enabled option, as natively).
errorbooleanfalseError state — error colors, aria-invalid, and errorText. Ignored while disabled.
errorTextReactNode—Shown instead of supportingText (with role="alert") when error is set.
fullWidthbooleanfalseStretch the field to the container's width.
inputRefRef<HTMLSelectElement>—Ref to the native <select> (the forwarded ref points at the root).
labelReactNode—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.
startIconReactNode—Icon at the start of the field (decorative).
styleCSSProperties—Applied to the field's root element.
supportingTextReactNode—Helper line below the field. Replaced by errorText while in error.
tonalbooleanfalseExpressive tonal style, as TextField tonal. @experimental Compose still marks these values TODO(b/448727879).
valuestring—Controlled selected value.
variant"filled" | "outlined"filledText field container style.

Option​

PropTypeDefaultDescription
childrenReactNode—The option's label. Also what the field shows once it is selected.
disabledbooleanfalseVisible, inert, and skipped by keyboard navigation.
startIconReactNode—Icon at the start of the option (decorative, 24dp).
supportingTextReactNode—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.
textValuestring—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).