Components › Forms & input › Select › Picker menu

Picker menu

canon

Anchored menu of items with optional check marks and icons.

Slots added 27 Sep 2026 (acrm !3909, wave D): separator rows `{ key, sep: true }` with role=separator (edge/double separators dropped, same rule as Menu button), per-item `ariaLabel` for rows whose visible text is shorter than the full sentence, and a clear row (`clearLabel`/`clearIcon`/`onClear`) — role=menuitem, no check, NOT danger (clearing a value is reversible; red is only for deleting data). No group headings. Registry repaired 17 Oct 2026 (review F6): added exported PickerMenuPanel to react[].

Used in 51 files · 36 areas · 64× — components/AppShell, components/FilterBar, components/ai-employees, components/assistant, components/calendar, components/callwrapup …

Select-one menu on any trigger. React <PickerMenu> (packages/ui/src/components/PickerMenu.tsx) takes the trigger as a render prop (it receives open), anchors the DS <Popover> to it and renders .acrm-popover-item rows: optional 18px icon or stage dot · name (one line, ellipsis) · accent check on the current item — dropdown type A. It closes on pick and ignores a pick of the current value.

When to use

  • Switching a view, stage, mode or layout from a button that is not a form field: record view switch, deal stage from a tile, sort key.
  • A row needs an icon or a colour dot (dot → .stagedot, var(--ic-*)). No identifier or note on the right — no grey trailing tag (owner 28 Sep 2026, ACRM-375–377); the desc prop is gone.
  • Any trigger shape — .tchip (Nav select), .pipe-btn, a tile, a section title — the menu is the same.

When not to use

  • A form field with a framed trigger — use Select.
  • Composing a set — use Check menu (menuitemcheckbox, stays open).
  • Records (people, companies, deals) — Record select. A long closed list of options (fields, templates) stays a Picker menu with searchPlaceholder.
  • Actions rather than a value — Menu button (or MenuPanel), never a bare Popover.

Anatomy

  • Anchor: span[display:inline-flex] wrapping the caller's trigger; click toggles unless disabled.
  • Shell <Popover role="menu">: .acrm-popover fixed, z-index: 101, 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; align start/end to the anchor edge; scrim at 85.
  • Row button.acrm-popover-item[role=menuitemradio]: 500 13px/20px, padding: var(--sp-2) var(--sp-3) → 32px, gap: var(--sp-3), --radius-sm, hover --hover-surface.
  • Slots: .ic 18px grid, --fg-secondary, --ico-sm — holds <Icon> or .stagedot 9px circle; .tx > .nm (ellipsis, --ink-900); .chk --accent --ico-xs, .off = visibility: hidden.
  • Groups: .acrm-popover-sep 1px --border — the only allowed separator; no eyebrow, no title (header is only the invisible aria-label).
  • Empty / loading: .acrm-popover-empty (400 13px --fg-muted, centered) instead of rows — pass items=[] and the sentence in emptyLabel („Načítám…", „Šablony se nepodařilo načíst."); a menu is never blank.
  • Search (optional, searchPlaceholder, acrm !4312): the canonical .acrm-recordselect-search row above the rows; on desktop the list scrolls under a standing search. Filtering folds diacritics („mest" finds „Město"), a separator stays only between two non-empty groups, Enter picks the first visible row, no match shows emptyLabel (default „Nic nenalezeno"). The query resets when the menu closes; focus lands in the search even when it appears after the menu opened (items loaded on open). Use it for a long closed list (filter fields, mail templates), not for records.
  • width: fixed desktop width for a searchable list, so the menu does not jump while typing.
  • Phone (< 720px): BottomSheet — grab, title (= header, now --h2), the search at the same left edge as the row icons, rows, full-width Hotovo. Inside another sheet (a picker opened from a sheet) it stays an anchored panel above it — never a sheet over a sheet.

PickerMenuPanel — the same menu without its own trigger

PickerMenuPanel is the body of Picker menu with a host-owned anchor and open state (anchorEl, open, onClose) — for two triggers of one menu (mail templates: the footer tool and the rail header) or a trigger that must stay the host's. value={null} makes it a menu with no current value (no check), e.g. inserting a template is an action that may be repeated.

Best practices

Do
  • Use dot for stage menus so the colour matches the stage tile and rail (var(--ic-blue) etc.).
  • Don't add a grey note to a row (count, IČO, „neaktivní", „výchozí") — the row is its name only.
  • Style the trigger's open state from the render-prop open (.tchip.open, .rvsw-btn.open).
  • Put a destructive item last after a .acrm-popover-sep when a menu mixes a value with an action — better: split into a Picker menu and an action popover.
Don’t
  • A heading, section label or search row inside a Picker menu — that is a Record select "in menu" variant.
  • Two check marks or a check on action rows — the check is selection state only.
  • Bold names or a .sub second line — removed 21 Aug 2026, not coming back.
  • A page-level copy (.rvsw-menu, .xyz-menu) — this component replaces them.

Content

  • Item names: nouns in sentence case ("Aktivity", "Profil", "Nabídka"); no trailing period.
  • Menu name (header) is only for assistive tech: a noun that names the choice ("Zobrazení", "Fáze").
  • Empty text: one line, "Žádné položky".
  • popover (family Popover) — the shell and action menus.
  • nav-select — .tchip + Picker menu.
  • check-menu — the multi counterpart.
  • select — same rows on a form trigger.
  • Stories: components--picker-menu--default, components--picker-menu--variants (plain / icon / stage dot / tag), components--picker-menu--states (trigger rest & disabled, open, danger + separator), components--picker-menu--with-icon-kbd, components--picker-menu--composition (section title trigger + kebab); legacy components--popover-menu--picker-menu.

Source: docs/select/picker-menu.usage.md · GitLab