Command palette

The single ⌘K surface of the app: search, commands and page actions in one overlay. React <GlobalSearch> (apps/web/src/components/GlobalSearch.tsx) plus apps/web/src/components/search/*; CSS apps/web/src/ds/global-search.css. It reuses the Dialog shell (.acrm-dialog-scrim + .acrm-dialog) and draws every result through the canonical Item list — the palette owns no row of its own.

Approved by the product owner on 7 September 2026 as deployed on dev. The code is the source of truth; the E1 wireframe that this was built from (:3777/preview/hledani-v2-palette.html) is history and is marked as superseded.

When to use

  • Getting to a record from anywhere: people, companies, deals — one keystroke, no navigation.
  • Running a command without leaving the current screen ("Nová osoba", "Naplánovat schůzku").
  • Acting on the record under the cursor — the right pane carries its facts and its actions.
  • Reaching a page by name instead of by clicking through the navigation.

When not to use

  • Filtering a list that is already on screen — that is Filter bar, not the palette.
  • Picking a related record inside a form or a dialog — that is Record select.
  • A hover glance at an entity — that is Entity preview popover; the palette is for deliberate work, the popover for a glance.
  • Bulk work over a chosen set — the palette offers bulk actions over an existing selection, it does not build the selection.

Anatomy

  • Shell: Dialog scrim (z-index: 90) + .acrm-dialog, width min(800px, 94vw) — the "800 palette" role of the dialog ladder (UI-RULES §10). Grid auto 1fr auto: query row, body, footer.
  • Query row .gsp-q: height 52px, bottom hairline, glass glyph, input 400 15px/1.25 var(--font-sans), Esc chip (.acrm-kbd). An active narrowing shows as a token .gsp-pre before the input — the prefix character itself leaves the field. Since 8 Sep 2026 the token is a blue pill (--accent-tint / --accent-ink, 22px) carrying the prefix glyph plus its label; the glyph takes no explicit size (1em from font: var(--small)) and the pill's left padding is one step smaller than the right, because the Tabler drawing already carries an inset.
  • Body .gsp-body: fixed height: 460px, flex column. On desktop it holds SplitView (.gsp2, asideWidth="minmax(0, 220px)", divider="line") — results left, detail of the row under the cursor right. Each column scrolls on its own.
  • Results: canonical Item list, .il .il-sm .il-embedded .il-seamless — row height --row-h-nav (28px), no dividers between rows (the hairline belongs to the query row only). Lead is an avatar or a type glyph and .il-t the name — nothing else in the row (no .il-meta: no "why found" reason, no key hint on the active row; acrm !4144, 29 Sep 2026). Why a record matched is carried by its section ("Podle významu"), by the highlight in the name and by the matched field in the detail pane; the key is shown once, in the action panel ("Otevřít ↵"), and the active row is recognised by its fill.
  • Section label .gsp-sec: a list divider, not a page heading — --label in --fg-secondary, count in --small, sentence case, no frame, no caps. Fixed section order Lidé → Firmy → Obchody; an empty section is not drawn. A failed section carries .err in --critical-ink on its own label.
  • "dalších N" .gsp-more: a quiet row in the rhythm of the list that loads more into the section — a continuation of the listing, not a button and not a narrowing.
  • Locked rows: rows outside the user's scope are found and only marked — drawn statically at the end of their section, name only, masked sub-label, no click. The order arrives from the server and the client never re-sorts it.
  • Detail pane .gsp2-detail: padding and gap --sp-5. Header .gsp2-head (avatar or action glyph + .gsp2-name: .t name, .gap, .k kind — the kind is the LAST WORD of the name, inline in quiet --p --fg-secondary, 8px (--sp-3) after it, so on a multi-line name it ends the last line; the centre of the mark sits on the centre of the FIRST line; the name clamps at 5 lines and on overflow the kind stays visible behind the single ellipsis at the end of line 5 — useClampedName, ACRM-381), facts in the definition grid .gsp2-def (20px minmax(64px, max-content) minmax(0, 1fr), column gap --sp-4) whose icon column and gap match .il-lead and .il-row exactly, so a fact glyph sits on the same vertical as the action glyph below it. Actions sit at the bottom of the column on a canonical Item list, separated by a hairline.
  • Match highlight: <mark> in --accent-tint / --accent-ink, radius --radius-xs — the same recipe the meeting transcript search already uses, in both columns.
  • Status strip .gsp-strip: --warning-subtle / --warning-ink. It states what the surface can still do and stays visible even after results arrive (R17). ⚠️ Its CSS header still describes it as sitting "above the footer, outside the body", but since the two-column rework the code renders it inside leftColumn (GlobalSearch.tsx:1400) — i.e. inside the 220px results column, where it wraps onto two lines. Measured, open for the AD (see docs/REVIEW-command-palette-family.md A2).
  • Footer .gsp-foot: a full-width button, 44px, --small in --fg-muted on --surface-75, always visible so nothing jumps after the first character. Carries the query in quotes and the ⌘↵ chip; it only pre-fills the assistant composer, it never sends.
  • Empty state: the left column lists recently opened records, "Vytvořit" and "Stránky"; the right column carries the prefix help.
  • "Nothing found": .acrm-empty with the query in the sentence, and under it .gsp-acts / .gsp-act — two sibling actions ("Založit osobu" / "Založit firmu"), never an edit of .acrm-empty itself. The actions centre with the sentence (ACRM-161, 8 Sep 2026): they answer it, so they share its axis. Until then .gsp-acts had no justify-content and both buttons sat at 0px from the left edge of an 800px dialog.

Prefixes

> was tried for commands and dropped: on a Czech keyboard it needs right-Alt, so it is now ordinary text with no token (palette-model.ts:345). Anything still documenting > is out of date.

Four, and the character never stays in the field — it becomes the .gsp-pre token, so the query reads the same with or without it. Backspace on an empty field removes the token.

prefixnarrows to
@Lidé — people only
#Firmy — companies only
:Příkazy — commands and pages
?Asistent — a question for the assistant

Mobile (≤ 720px)

The palette is the screen: fixed inset 0, no scrim, no radius, and it carries its own z-index: 90 because there is no scrim to provide one. SplitView is not mounted at all — the body holds either the list or the detail, never both, and the detail is a second step reached by opening a row, with .gsp-back above it. Esc gives way to a textual "Zrušit" (.gsp-cancel).

Best practices

Do

  • Draw every result through Item list; the palette owns the shell and the section label, never a row of its own.
  • Keep the footer visible from the moment the palette opens — it is an exit, not a result.
  • Let the server decide row order, including where locked rows sit.
  • Keep the detail pane's fact grid on the same vertical rhythm as the list rows.

Don't

  • Don't put a grey reason ("klíč", "význam", "telefon") or a key hint into a result row — the section, the highlight and the detail pane already say it (no grey explanatory text, ACRM-375–377).
  • Don't put the record kind on its own line under the name, and don't let a long name push it out of view — the kind follows the name (ACRM-381).
  • No dividers between result rows — use .il-seamless. A hairline between short rows turns a selection into a table.
  • No chips on rows; the type is carried by the lead glyph.
  • No frame around a section — the panel is already a frame.
  • No spinner while loading; a skeleton in the row rhythm keeps the layout from re-flowing.
  • No full-screen error when one section fails — the failure belongs to that section's label.
  • No empty row: when there is no company or city, the sub-label carries the last activity.

Content

Sentence case everywhere, no caps, no trailing periods on labels. Section labels are plain nouns ("Lidé", "Firmy", "Obchody", "Vytvořit", "Stránky").

  • Item list — every row of the palette, including the .il-seamless variant this surface established.
  • Dialog — the shell, the scrim and the elevation.
  • Entity preview popover — the glance counterpart; both take their facts from the same place, so the two may differ in form but never in content.
  • Split view — the two-column body, in its "list | reader" variant.

Source: docs/command-palette/command-palette.usage.md · GitLab