Skip to main content
Gelium UI
Blog View source 0.6.5 Theme Gallery
Recipe

Native behavior · Vercel · Gelium defaults · Native baseline

Appearance
Menu

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

Select props
PropTypeDefaultDescription
Options[]selectMenuOptionstandard, priority, enterpriseOptions rendered as native option elements; the closed vocabulary of the server-driven demo.
Errorstring""Server-side validation message; renders aria-invalid when set.
Valuestring""Option value submitted by the native select; unknown values return HTTP 422.
Labelstring""Visible option text in the picker.
SelectedboolfalseMarks 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-filled or ui-select-outlined to 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-thickness outline; error state is announced via aria-invalid plus visible error text, never color alone.
  • A picker's placeholder option is disabled so 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 disabled to 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 use role=listbox / role=option, and works as a normal form submission without JavaScript.
  • Modern platform popover: a separate HTML Popover API trigger and popover element. It displays only when the browser supports both :popover-open and 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-thickness outline; error state is announced via aria-invalid plus visible error text, never color alone.
  • A picker's placeholder option is disabled so assistive tech and users never confuse it with a real selection.