Select

Choose one option from a styled list.

StableSource
Region

Import

tsx
import { Select } from "@merid/react";

Anatomy

tsx
<Select.Root>
  <Select.Trigger />
  <Select.Content>
    <Select.Item value="a">A</Select.Item>
  </Select.Content>
</Select.Root>
  • Root holds the value and open state, and renders a hidden <input> when name is set.
  • Trigger is the role="combobox" button. It shows the selected label or the placeholder.
  • Content is the portalled role="listbox". It stays mounted (hidden) so options can report their labels.
  • Item is one role="option". Its text children are the label, shown in the trigger and used for typeahead.

Examples

Controlled

Pass value and onValueChange. An empty string means nothing is selected and shows the placeholder.

Value: (none)

Sizes and disabled

In a form

Set name (and optionally required and form) on Root. A hidden input carries the value on submit.

tsx
<form>
  <Select.Root name="region" required>
    <Select.Trigger aria-label="Region" />
    <Select.Content>…</Select.Content>
  </Select.Root>
</form>

API reference

Root

PropTypeDefaultDescription
valuestring–Controlled selected value ("" means nothing selected).
defaultValuestring""Initial value when uncontrolled.
onValueChange(value: string) => void–Called with the newly selected value.
openboolean–Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => void–Called when the open state should change.
namestring–Form field name; renders a hidden input carrying the value.
requiredboolean–Marks the hidden input as required.
disabledbooleanfalseDisable the whole select.
placeholderReactNode"Select…"Shown in the trigger when no value is selected.
formstring–Associates the hidden input with a form by id.
childrenReactNode–Trigger and Content.

Trigger

Accepts every <button> attribute except value. Children, when given, replace the displayed label. An id you pass is kept (so <label htmlFor> works) and the listbox stays labelled by it. Inside a Field the trigger picks up the wiring like every Merid control: the field's id and label (aria-labelledby), aria-describedby for the description and error, aria-invalid + data-invalid, aria-required and disabled. Explicit props win.

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Control size.
invalidboolean–Marks the field invalid: `aria-invalid` for semantics, `data-invalid` for the danger border. Inside a Field, derived from its error.
refRef<HTMLButtonElement>–Forwarded ref to the combobox button.

Content

Accepts every <div> attribute. The listbox matches the trigger's width.

PropTypeDefaultDescription
placementPlacement"bottom-start"Preferred placement.
sideOffsetnumber6Distance from the trigger in px.
containerElement | nulldocument.bodyPortal target. `undefined` uses `document.body`; `null` renders nothing until the target exists.
refRef<HTMLDivElement>–Forwarded ref to the listbox.

Item

Accepts every <div> attribute except children. Your onClick and onPointerMove run first; call event.preventDefault() in them to skip selecting or highlighting the option.

PropTypeDefaultDescription
valueRequiredstring–Value submitted and reported by onValueChange.
childrenRequiredstring–Visible label. Also used in the trigger and for typeahead.
disabledbooleanfalseDisable the option.

Styling

ClassElement
.mrd-select__triggerTrigger
.mrd-select__value, .mrd-select__iconTrigger label and chevron
.mrd-select__contentListbox
.mrd-select__item, .mrd-select__item-text, .mrd-select__checkOption, its text, the selected check
AttributeOnValues
data-stateTrigger, Contentopen, closed
data-sizeTriggersm, md, lg
data-placeholderTriggerpresent when no value is selected
data-invalidTriggerpresent when invalid (drives the danger border)
data-activeItempresent on the keyboard/pointer-highlighted option
data-disabledItempresent when disabled
aria-selected="true"Itemthe selected option

The trigger height comes from --mrd-select-height, set per data-size.

Accessibility

Follows the WAI-ARIA Select-Only Combobox pattern. Focus stays on the trigger; the highlighted option is exposed through aria-activedescendant. Give the trigger an accessible name with aria-label, aria-labelledby, or an id referenced by a <label htmlFor>.

KeyAction
EnterOpen, or select the highlighted option and close.
SpaceOpen, or select the highlighted option and close.
ArrowDownOpen, or highlight the next option.
ArrowUpOpen, or highlight the previous option.
HomeHighlight the first option.
EndHighlight the last option.
A–ZTypeahead. When closed, selects the match directly.
TabSelect the highlighted option, close and move focus on.
EscapeClose without changing the value.

Guidelines

Do

Use Select for 5–15 options where the list itself is familiar (countries, regions, plans).

Avoid

Use Select for two or three options — show them as Radio or a SegmentedControl.

Do

Always pair the trigger with a visible label.

Avoid

Rely on the placeholder as the only label; it disappears once a value is chosen.

  • NativeSelect — the platform <select>, best on mobile and for long lists.
  • DropdownMenu — actions, not values.
  • Field — label, hint and error around a control.