Mention input

A text field whose @ references become inline entity chips. This is an app composition: the React implementation is apps/web/src/components/MentionInput.tsx (not in @acrm/ui), the styles are apps/web/src/ds/mention-input.css. It is a contenteditable surface styled like the Input box, single-line (.mi-editor) or multi-line (.mi-editor.mi-multiline), with a caret-anchored autocomplete menu fed by GET /mentions/search. Chips are <EntityRef variant="mini"> markup (span.eref-chip.eref-mini > .eref-chip-nm), the menu is the canonical .acrm-popover shell with .acrm-recordselect-opt rows. The catalog renders the emitted markup statically.

When to use

  • Task text and note composer where the user refers to a person, company, deal, product or colleague by typing @.
  • Chat-style composers (onSubmit: Enter sends, Shift+Enter breaks the line).
  • Read-only rendering of the same text goes through MentionText — same chips, no editing.

When not to use

  • Plain text without references — Input or Textarea; the contenteditable surface costs more than it gives.
  • Picking one record as a field value — Record select (the rail owner/tags pickers use RailPickerMenu).
  • Free tags — Tag input; an @ chip is a reference, not a label.
  • Rich formatting (headings, lists) — the note editor; Mention input keeps text plain plus chips.

Anatomy

  • Editor .mi-editor: min-height: 32px; padding: var(--sp-2) var(--sp-3), border: 1px solid var(--border-strong), border-radius: var(--radius-sm), background: var(--surface-100), font: var(--p), line-height: 1.5, width: 100%, white-space: pre-wrap, cursor: text; .mi-multiline raises it to min-height: 88px.
  • Placeholder: data-placeholder shown through :empty::before in --fg-muted (contenteditable has none natively).
  • Focus: border-color: var(--accent) + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent).
  • Chip: .eref-chip.eref-mini — inline, padding: 0 1px, --radius-xs; name .eref-chip-nm 12px, --fg-secondary, dashed --border-strong underline; hover --ink-900 with --ink-500 underline. Click opens the record; data-* carries type and id. Lost entity = .mi-lost plain text in --fg-secondary.
  • Menu .mi-menu: position: fixed; z-index: 101, min-width: 280px, max-width: min(380px, 100vw - 16px), max-height: min(344px, 100vh - 16px) (~8 rows, scrolls), padding: var(--sp-2), --surface-100, 1px --border-strong, --radius-md, --shadow-pop; no scrim, closes on blur/Esc. Anchored to the caret line, flips above when out of room.
  • Row: .acrm-recordselect-opt (32px, --radius-sm, hover --hover-surface) > .mi-opt-ic 24px slot with an 18px icon in ic-tone-{blue,purple,green,rose} per type (contact blue, organization purple, deal green, user/product rose) + .acrm-recordselect-tx (.nm 500 13px/20px, .em 300 12px --fg-secondary; the last .em is the type label and never truncates).
  • Empty state: .acrm-recordselect-empty; a visually hidden .mi-sr live region announces the result count.
  • Props of note: multiline, placeholder, ariaLabel, onChange (value with @[type:id|display] tokens), onMentionsChange, onSubmit, submitOnModEnter.

Best practices

Do

  • Use it wherever the text may name a record; the token grammar (@acrm/shared) is shared, so the same value renders everywhere.
  • Keep the field width like a text field (column width); the menu sizes itself to content.
  • Show the chip as the record's display name only; the type is visible in the menu row, not in the chip.
  • Choose submitOnModEnter for note composers (⌘/Ctrl+Enter sends) and onSubmit for chat (Enter sends).

Don't

  • Another mention or chip implementation — this is the only one; the component emits .eref-chip (the old .mi-chip-* classes were dead CSS and were removed in acrm !4058, 28 Sep 2026).
  • An external rich-text library or a token visible to the user (@[contact:…] must never leak into the UI).
  • A heading or eyebrow inside the menu; a group separator is the only allowed divider.
  • Colouring the chip by type — mini EntityRef is neutral; the coloured glyph lives in the menu only.

Content

  • Placeholder is a short prompt: "Add a note…", "Write a message…"; mention the @ only where users would not expect it ("Type @ to mention").
  • Menu rows: name first, identifier (e-mail, domain) second, type label last ("Contact", "Company") — from common:mention.type.*.
  • Empty state text is one line ("No results"); the minimum query length is MENTION_MIN_QUERY.
  • A lost entity renders its last known display name in plain text — never "[deleted]" inside the chip.
  • entity-ref (variant mini) — the chip.
  • record-select — the row anatomy and the type B search panel.
  • textarea — multi-line text without references.
  • popover — the menu shell.
  • Stories: components--mention-input--default, components--mention-input--sizes, components--mention-input--states, components--mention-input--composition.

Source: docs/text-field/mention-input.usage.md · GitLab