MentionPicker
<caos-mention-picker>
the shortlist of people and records offered while somebody types an @mention
May be placed on a record page, an app’s home page and a page inside an app.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
candidates |
property only | Who or what may be chosen, in the order they should read. Drawn exactly as given — this piece never filters or re-orders, because whoever fetched them applied the access rules and a second opinion here would disagree with theirs. Each entry is { kind, ref, label, hint? }. Setting them puts the highlight back on the first row. |
json | empty list | Optional |
emptyLabel |
empty-label, markup only |
What is said when nothing matched. Worth setting: the default says a true thing about a list and nothing about what the reader could try instead. | text | No matches. | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
choose |
Somebody clicked a row, or the surface called chooseActive because they pressed Enter. Nothing has been written into any text box — turning the choice into a mention is the surface’s job, and insertMention is the function that does it. |
{ kind, ref, label, hint? } |
active-change |
The highlighted row changed. The id is what a composer puts in its own aria-activedescendant, which is how a screen reader follows a highlight that is moving in an element the reader is not focused on. |
{ index: number, id: string |
Nothing goes inside this piece.
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-color-surface |
Behind the list, so it reads as floating over the page. |
--caos-color-border |
The list’s edge. |
--caos-shadow-float |
The lift that separates the list from what is under it. |
--caos-color-text |
The names. |
--caos-color-text-subtle |
The second line that tells two identical names apart. |
--caos-color-text-muted |
The line shown when nothing matched. |
--caos-color-accent-fill |
Behind the highlighted row. |
--caos-color-accent-contrast |
The text drawn on that fill. |
--caos-font |
The typeface of the list. |
--caos-text-row |
The size of a name. |
--caos-text-label |
The size of the second line. |
--caos-space-1 |
The inset around the rows. |
--caos-space-2 |
Inside a row, and the gap between a name and its second line. |
--caos-radius-sm |
The roundness of the list and of the highlighted row. |
Methods
Section titled “Methods”| Name | Description | Arguments |
|---|---|---|
moveDown() |
Move the highlight down, wrapping at the bottom. What a composer calls when the reader presses the down arrow; this element hears no keys of its own. | not stated |
moveUp() |
Move the highlight up, wrapping at the top. | not stated |
chooseActive() |
Choose the highlighted row, raising choose. Returns what was chosen, or null when there was nothing — so a composer can tell “Enter chose somebody” from “Enter should send”. |
not stated |
Readable state
Section titled “Readable state”| Name | Type | What it tells you |
|---|---|---|
activeIndex |
number | Which row Enter would choose. Always within the list; 0 when the list is empty. |
active |
the highlighted candidate, or null | What Enter would choose. Null when there is nothing to choose. |
activeOptionId |
text, or null | The id of the highlighted row, for the composer’s aria-activedescendant. Null when there is nothing highlighted. |
Styling parts
Section titled “Styling parts”| Part | Which piece of it | When it is there |
|---|---|---|
::part(list) |
the floating list itself | Always |
::part(option) |
one choosable row | Always |
::part(label) |
the name in a row | Always |
::part(hint) |
the second line in a row | that candidate was given one |
::part(empty) |
the line shown when nothing matched | there are no candidates |
When to use it
Section titled “When to use it”Somebody is typing a mention into prose and needs to say who they meant without leaving the box. The list is short, it is driven by the keys they are already pressing, and it disappears when the mention is finished.
What to use instead
Section titled “What to use instead”- LookupField — A record is being CHOSEN AS A VALUE rather than named inside a sentence. That is a field with a label, a stored id and a save behind it, none of which a mention has.
- OverflowMenu — The list is a set of actions to invoke rather than candidates to name. A menu takes focus and owns its keyboard, which is the opposite of what this does.
- PicklistField — The choice is one of a declared list of values rather than a person or a record. That list is fixed and known in advance; this one is fetched for every keystroke.
Accessibility
Section titled “Accessibility”It is a listbox that the text box beside it OWNS — the combobox pattern. It never takes focus and has no keyboard handler: the composer keeps focus, forwards the arrows through moveUp/moveDown, and points at the highlighted row by putting activeOptionId into its own aria-activedescendant. Rows carry role="option" and aria-selected, so the highlight is announced as a selection rather than only drawn. Clicking a row is handled on mousedown with the default prevented, which is what stops the click from blurring the text box and losing the caret the insertion needs. The “nothing matched” line is role="presentation" rather than an option, because announcing it as a row tells a reader there is something to pick when there is not. Left to the author: naming the list, with aria-label — it is named “Mention suggestions” by default, which is right for one list on a page and wrong for two.
Examples
Section titled “Examples”People matching what was typed
Section titled “People matching what was typed”The second line is what tells two people with the same name apart.
Cannot be shown as a page placement: aria-label, which is set on the element rather than declared as an input. A stored placement carries only a component’s own inputs, so a page copying this would place the piece without it.
<caos-mention-picker id="mentionPicker1" aria-label="Mention suggestions"></caos-mention-picker>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const mentionPicker1 = document.getElementById('mentionPicker1');mentionPicker1.candidates = [ { "kind": "person", "ref": "3f4c1b2a-5d6e-4f70-8a91-b2c3d4e5f607", "label": "Morgan Hale", "hint": "morgan.hale@example.test" }, { "kind": "person", "ref": "8b7a6c5d-4e3f-4210-9876-543210fedcba", "label": "Morgan Iyer", "hint": "m.iyer@example.test" }, { "kind": "person", "ref": "1a2b3c4d-5e6f-4708-9a0b-1c2d3e4f5061", "label": "Morgana Reyes", "hint": "morgana@example.test" }];People and records together
Section titled “People and records together”A surface merges what the people lookup returned with what a record search returned; this piece draws the merged list in the order it was handed.
Cannot be shown as a page placement: aria-label, which is set on the element rather than declared as an input. A stored placement carries only a component’s own inputs, so a page copying this would place the piece without it.
<caos-mention-picker id="mentionPicker1" aria-label="Mention suggestions"></caos-mention-picker>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const mentionPicker1 = document.getElementById('mentionPicker1');mentionPicker1.candidates = [ { "kind": "person", "ref": "3f4c1b2a-5d6e-4f70-8a91-b2c3d4e5f607", "label": "Morgan Hale", "hint": "morgan.hale@example.test" }, { "kind": "record", "ref": "account/6d5c4b3a-2918-4706-b5a4-938271605f4e", "label": "Northgate Holdings", "hint": "Account" }, { "kind": "record", "ref": "project/0f1e2d3c-4b5a-4968-8776-655443322110", "label": "Northgate retrofit", "hint": "Project" }];Nothing matched
Section titled “Nothing matched”Said in the surface’s own words rather than left as a blank list.
Cannot be shown as a page placement: aria-label, which is set on the element rather than declared as an input. A stored placement carries only a component’s own inputs, so a page copying this would place the piece without it.
<caos-mention-picker id="mentionPicker1" aria-label="Mention suggestions" empty-label="Nobody here by that name."></caos-mention-picker>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const mentionPicker1 = document.getElementById('mentionPicker1');mentionPicker1.candidates = [];