Select

One value from a closed short list (≤ ~10 items). React <Select> (packages/ui/src/components/Select.tsx) renders a 32px field trigger .acrm-select-trigger (apps/web/src/ds/inputs.css §5) that opens the portalled DS <Popover> (.acrm-popover, apps/web/src/ds.css) with .acrm-popover-item rows and an accent check on the selected row — dropdown type A. It is never the native OS menu: a hidden select.acrm-select-native (sr-only, position: fixed) only backs the form (name, required, FormData, constraint validation).

When to use

  • One value from a closed list of up to ~10 options: country, pipeline, type, status.
  • A form field that must submit with the form (name, required) — the hidden native select carries it.
  • A drop-in replacement for an existing native <select>: the API keeps <option> children, value and onChange(e) with e.target.value.

When not to use

  • More than ~10 items or records (people, companies) — use Record select (search row).
  • Several values — use Multi-select.
  • Free text with suggestions from an external source (address, ARES) — use Combobox.
  • A view/sort/filter switch in a section top bar — use Nav select (.tchip).
  • A native <select> for the visible control — flagged by pnpm ds:validate:usage; the only allowed native select is the sr-only backing one inside this component.

Anatomy

  • Trigger button.acrm-select-trigger: display: flex, width: 100%, height: 32px, padding: 0 var(--sp-3), gap: var(--sp-3), background: var(--surface-100), border: 1px solid var(--border-strong), border-radius: var(--radius-sm), font: var(--p), color: var(--ink-900); hover --surface-75.
  • Value .acrm-select-val (ellipsis) + 16px chevron in --fg-muted.
  • States on the trigger: .is-placeholder = --fg-muted text; .is-open = --accent border + 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); .is-error = --critical border + 16% halo; :disabled = --surface-75, --border, --fg-muted, no shadow; .is-select-ignored = muted text (import mapping).
  • Menu .acrm-popover: position: fixed, z-index: 101 over a scrim at 85, min-width: 180px, max-width: min(360px, 100vw − 16px), max-height: min(60vh, 420px), padding: var(--sp-2), --surface-100, 1px --border-strong, --radius-md, --shadow-pop; width = trigger width (offsetWidth).
  • Row button.acrm-popover-item[role=option]: font: 500 13px/20px, padding: var(--sp-2) var(--sp-3) → 32px, --radius-sm, hover --hover-surface; .tx > .nm (ellipsis) + .chk (accent, --ico-xs) hidden with visibility when .off; :disabled = opacity: 0.4.
  • Hidden select.acrm-select-native: tabindex=-1, aria-hidden, 1×1px, clip-path: inset(50%).
  • No header, no eyebrow above the rows; groups only by .acrm-popover-sep (1px --border, margin: var(--sp-1) 0).

Best practices

Do

  • Wrap the field in <Field label>; the label is the accessible name via aria-labelledby, or pass aria-label for a bare trigger.
  • Keep the option label short and final — one line, no description; no secondary identifier, no trailing tag and no second line (no grey trailing tag (owner 28 Sep 2026, ACRM-375–377)).
  • Let the trigger take the width of its container (width: 100%) and size the container to the longest option.
  • Pass placeholder (or an empty-value <option>) when the field can be unset; an uncontrolled select without defaultValue picks the first option like a native select.

Don't

  • A <Select> with 20 countries or 40 users — it has no search; that is Record select.
  • is-open styling on hover or a shadow on the resting trigger — fields are flat at rest, elevation belongs to the open menu only.
  • An eyebrow or title inside the menu ("Pipeline") — the trigger's label already says it.
  • A colour fill for the selected row — selection is the accent check on a plain row.

Content

  • Trigger placeholder: an imperative with an ellipsis — "Vyberte…" (default), "Vyberte pipeline…"; sentence case.
  • Options are nouns in sentence case, no trailing period, no "Please select".
  • Error text goes under the field in .acrm-field-error, not into the trigger; the trigger only gets className="is-error".
  • Dates in options follow the DS year rule ("26. 8." / "14. 11. 27"); numbers stay proportional, never tabular-nums.
  • multi-select — same trigger, checkbox rows, menu stays open.
  • record-select — search row for > ~10 items or records.
  • combobox — free text with suggestions.
  • picker-menu — the same type A rows on any trigger.
  • popover (family Popover) — the shell.
  • Stories: components--select--default, components--select--states, components--select--composition, components--select--dropdown-types (types A–D side by side); legacy components--inputs--select.

Several values — multiple (acrm !4472, 3 Oct 2026)

Picking several values from the same closed list is a prop of Select, not a separate component („vícenásobný výběr je prop, ne komponenta“). It replaced MultiSelect, which is gone.

  • Same trigger and menu as the single select. Each row carries the DS checkbox on the right (.acrm-checkbox); the menu stays open after a click so the user can tick several items.
  • The value is an array of keys in the order they were picked (a new pick goes to the end); changes come through onValuesChange(values). The hidden native select is multiple, so onChange(e) and form submission keep working.
  • The trigger summarises the choice: one or two labels joined („A, B“), three and more as „N vybráno“, or your own summary(selected). Empty shows the placeholder.
  • Use it for short closed lists (roughly up to 10 items). A long or searchable list is RecordSelect with chips (adding mode), not Select multiple.

Source: docs/select/select.usage.md · GitLab