Published · Updated · v0.6.5 · MIT License · Source: select.md
Select
Select is a dropdown picker built on the native <select> element with Material 3 field styling. The browser element stays the focusable, operable control. Use a select when people must choose one option from a known list and the choice submits through a normal <form>. The native element keeps the no-JS flow and keyboard behavior for free. No component JavaScript is required.
Examples
Filled and outlined fields
The two field variants share the native select as the focusable control.
Filled
Outlined
{{template "select-demo" .}}
Server-driven menu
The menu posts its value and the server validates it against a closed vocabulary.
{{template "select-menu-demo" .}}
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
Options | []selectMenuOption | standard, priority, enterprise | Options rendered as native option elements; the closed vocabulary of the server-driven demo. |
Error | string | "" | Server-side validation message; renders aria-invalid when set. |
Value | string | "" | Option value submitted by the native select; unknown values return HTTP 422. |
Label | string | "" | Visible option text in the picker. |
Selected | bool | false | Marks the server-side default selection. |
Guidance
When to use
Use a select when people must choose one option from a known list. That means six or more options, or options with long labels. The choice submits through a normal <form>.
When not to use
Do not use a select when the options should stay visible and comparable. Five or fewer choices belong in a Radio group. For independent multi-select, use Checkboxes. When choosing should immediately perform an action instead of submitting a value, a Menu is the command surface.
Usability
- Wrap with
ui-select-filledorui-select-outlinedto pick the variant. - The first
<option value="" selected disabled>is a placeholder: it keeps visible picker text while the field paints it transparent until a real option is chosen. - The label doubles as the placeholder and floats once the field is focused or a real option is selected.
<div class="ui-select ui-select-filled">
<select id="plan" name="plan">
<option value="" selected disabled>Choose a plan</option>
<option value="standard">Standard</option>
<option value="priority">Priority</option>
</select>
<label class="ui-select-label" for="plan">Plan</label>
<span class="ui-select-caret" aria-hidden="true">
<svg focusable="false" viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="m7 10 5 5 5-5z"></path>
</svg>
</span>
</div>
Accessibility
- The visible
<label class="ui-select-label" for="...">is a real label that focuses the select. - The native element carries name/value semantics for form submission and assistive technology.
- Focus is announced with the shared
--ui-focus-thicknessoutline; error state is announced viaaria-invalidplus visible error text, never color alone. - A picker's placeholder option is
disabledso assistive tech and users never confuse it with a real selection.
See Choose the right control for the cross-component decision.
Variants
- Filled (
ui-select-filled) — surface-container-highest background, top-only 4px radius, and a bottom active indicator. Focus turns the indicator to the primary color. - Outlined (
ui-select-outlined) — transparent field with a 1px outline; focus grows the outline to the primary color.
States
- Normal — a floating label sits centered until the field is focused or a value is chosen. Then it floats to the top.
- Populated — the label floats whenever a real option is selected (
:has(select option:checked:not([value=""]))), even without focus. - Disabled — add
disabledto the native element. The field dims its content and stops interaction. - Error — add
aria-invalid="true"to the select (plus an error message in the form, beyond color). The outline and label turn to the error color.
The label doubles as the placeholder. The first <option value="" selected disabled> keeps its visible picker text (for example Choose a plan), so the native popup row reads clearly. The field paints that text transparent until a real option is chosen — the resting label is the only prompt in the closed field. The label floats once the field is focused or a real option is selected.
Design tokens
Field anatomy reuses the --ui-field-* palette tokens shared with Text field. Select-specific tokens are:
| Token | Role |
|---|---|
--ui-select-height |
Field height (56px) |
--ui-select-radius |
Outlined field corner radius |
--ui-select-radius-top |
Filled field top-only corner radius |
--ui-select-caret |
Caret icon color |
--ui-select-fg |
Selected value text color |
--ui-select-label |
Resting floating label color |
--ui-select-outline |
Resting outline / active indicator |
--ui-select-container-filled |
Filled field background |
--ui-select-hover-outline |
Hover outline color |
--ui-select-focus |
Focus outline / indicator color |
--ui-select-error |
Error outline and label color |
--ui-select-disabled-opacity |
Disabled content opacity |
--ui-select-list-bg |
Native options popup background |
--ui-select-list-fg |
Native options popup text |
Does select work without JavaScript?
The control is the native <select>: keyboard navigation, form submission, and the options popup are browser behavior — zero component JavaScript. The floating label and field surface are pure CSS. In forced-colors mode the select keeps visible CanvasText / GrayText / Mark boundaries and gives the popup FieldText contrast.
HTMX select enhancements
When the resolved execution profile is htmx, the docs page also shows a separate Select Popup Experiment. It has two progressively layered alternatives. The baseline native select above remains the supported no-JavaScript Select in every browser.
- Cross-browser disclosure: native
<details>and<summary>plus ordinary submit buttons in a semantic list. This is visibly usable wherever the HTMX experiment renders, does not userole=listbox/role=option, and works as a normal form submission without JavaScript. - Modern platform popover: a separate HTML Popover API trigger and
popoverelement. It displays only when the browser supports both:popover-openand the CSS Anchor Positioning declarations used by the experiment. Its native buttons submit to the same endpoint. An HTMX response swaps the entire experiment root, naturally removing/closing the popover; without HTMX the normal POST+303 redirect preserves the selected value. Browsers that do not meet both feature checks see neither an inert trigger nor raw popover content.
The platform popover is Base UI docs-inspired and headless-only influence, not Base UI behavior or a popup clone. It intentionally does not provide roving focus, typeahead, portal collision logic beyond CSS anchor try fallbacks, or JavaScript focus return. Native browser button, form, and popover behavior remain the interaction model; use the native select when those richer interaction guarantees are required.
Accessibility
- The visible
<label class="ui-select-label" for="...">is a real label that focuses the select. - The native element carries name/value semantics for form submission and assistive technology.
- Focus is announced with the shared
--ui-focus-thicknessoutline; error state is announced viaaria-invalidplus visible error text, never color alone. - A picker's placeholder option is
disabledso assistive tech and users never confuse it with a real selection.