Components · Identity
AssigneePicker
People picker with avatars, "you" first, Unassigned, Assign to me and an avatar stack.
Assignee with you and Unassigned
import { useState } from 'react';
import { AssigneePicker, Field, FieldLabel } from '@rookcom/monarch-react';
import type { AssigneePerson } from '@rookcom/monarch-react';
const members: AssigneePerson[] = [
{
id: 'm1',
name: 'Priya Raman',
email: '[email protected]',
hint: '4 open',
group: 'In this team',
},
{
id: 'm2',
name: 'Jonas Weber',
email: '[email protected]',
status: 'online',
group: 'In this team',
},
{ id: 'm3', name: 'Amara Okafor', email: '[email protected]', group: 'Everyone else' },
{
id: 'm4',
name: 'Lucas Martin',
email: '[email protected]',
status: 'away',
group: 'Everyone else',
},
{ id: 'm5', name: 'Mei Chen', email: '[email protected]', group: 'Everyone else' },
];
function SingleDemo() {
const [value, setValue] = useState<string | null>('m2');
return (
<Field style={{ width: '100%', maxWidth: 300 }}>
<FieldLabel>Assignee</FieldLabel>
<AssigneePicker people={members} currentUserId="m3" value={value} onValueChange={setValue} />
</Field>
);
}
<SingleDemo />Usage examples
Several reviewers
import { useState } from 'react';
import { AssigneePicker, Field, FieldLabel } from '@rookcom/monarch-react';
import type { AssigneePerson } from '@rookcom/monarch-react';
const members: AssigneePerson[] = [
{
id: 'm1',
name: 'Priya Raman',
email: '[email protected]',
hint: '4 open',
group: 'In this team',
},
{
id: 'm2',
name: 'Jonas Weber',
email: '[email protected]',
status: 'online',
group: 'In this team',
},
{ id: 'm3', name: 'Amara Okafor', email: '[email protected]', group: 'Everyone else' },
{
id: 'm4',
name: 'Lucas Martin',
email: '[email protected]',
status: 'away',
group: 'Everyone else',
},
{ id: 'm5', name: 'Mei Chen', email: '[email protected]', group: 'Everyone else' },
];
function MultipleDemo() {
const [values, setValues] = useState(['m1', 'm2', 'm4', 'm5']);
return (
<Field style={{ width: '100%', maxWidth: 300 }}>
<FieldLabel>Reviewers</FieldLabel>
<AssigneePicker
multiple
people={members}
currentUserId="m3"
values={values}
onValuesChange={setValues}
allowUnassigned={false}
placeholder="Add reviewers"
/>
</Field>
);
}
<MultipleDemo />Purpose
Assigns one or several people (owners, assignees, reviewers) from a searchable list of avatar rows. It is EntityPicker preset for people: the product passes its members, Monarch handles avatars, ordering, the shortcuts and the trigger summary.
Use when
- Assigning a ticket, issue, task or alert to a person.
- Choosing reviewers, watchers or members (
multiple).
Avoid when
- Records that are not people: use EntityPicker.
- Mentioning people inside text: use a mention menu in the editor.
Design principles
- People are recognised by face and name: every row has an Avatar, with presence when the product knows it.
- The most likely choices come first: Unassigned, then you, then the product’s order (groups such as "In this team").
- The product owns the people: members come in as
peopleor fromonSearch; nothing is fetched by Monarch.
API reference
import { AssigneePicker } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
AssigneePicker | component | client | .mn-assignee-picker |
Accepts every EntityPicker prop except options, onSearch, valueOptions, clearOption, onCreate and renderValue, which it builds from people.
Forwards its ref to the trigger button. Values are person ids; Unassigned is null (single) or [] (multiple).
Generated from the TypeScript declarations of packages/react/src/components/identity/assignee-picker.
Properties
AssigneePerson is { id, name, email?, avatarUrl?, status?, hint?, group?, disabled? }.
AssigneePicker
People picker: assign one or several people from a searchable list with avatars, the current user first, an "Unassigned" option and an "Assign to me" shortcut. The trigger shows the assignee, or an avatar stack for several. Built on EntityPicker and Avatar; the product supplies the people.
| Prop | Type | Description |
|---|---|---|
align | "center" | "start" | "end" | Popover alignment against the trigger. Default `start`. |
allowUnassigned | boolean | Offer an "Unassigned" option that clears the selection. Default true. |
className | string | |
closeOnSelect | boolean | Close after a pick. Default: true, or false with `multiple`. |
contentClassName | string | Class on the popover panel. |
currentUserId | string | null | The signed-in user: listed first (within their group) as "(you)", with an "Assign to me" shortcut. |
defaultOpen | boolean | |
defaultValue | string | null | |
defaultValues | string[] | |
disabled | boolean | |
emptyMessage | ReactNode | Shown when nothing matches. |
filter | ((option: ComboboxOption, query: string) => boolean) | Custom filter for static `options`. Default: label, hint and keywords. |
footer | ReactNode | ((context: EntityPickerFooterContext) => ReactNode) | Content under the list, such as "Manage projects". A function gets `close` and the query. |
id | string | |
invalid | boolean | |
limit | number | Render at most this many matches, with a message to keep typing. |
loading | boolean | Shown while records load. |
maxAvatars | number | Avatars shown in the trigger before a "+N" tile, with `multiple`. Default 3. |
messages | (Partial<AssigneePickerMessages> & Partial<ComboboxMessages>) | Interface text, for translation. |
minSearchLength | number | Shortest query `onSearch` runs for. Default 0. |
multiple | boolean | Pick several records; the list stays open after each pick. |
onOpenChange | ((open: boolean) => void) | |
onSearch | ((query: string, context: ComboboxSearchContext) => Promise<AssigneePerson[]>) | Search people on a server instead of filtering `people`. Debounced and aborted like Combobox `onSearch`. |
onValueChange | ((value: string | null) => void) | |
onValuesChange | ((values: string[]) => void) | |
open | boolean | Whether the picker is open (controlled). |
people | AssigneePerson[] | The people to choose from, such as workspace members. |
placeholder | string | Trigger text when nothing is selected. Default "Select…". |
renderOption | ((option: ComboboxOption, state: ComboboxOptionState) => ReactNode) | Custom option content. Default: media, label, description, hint and a check mark. |
searchDebounce | number | Milliseconds to wait after typing before `onSearch`. Default 250. |
searchLabel | string | Accessible name of the search box. Default: `searchPlaceholder`. |
searchPlaceholder | string | Placeholder of the search box. Default "Search…". |
showAssignToMe | boolean | Show the "Assign to me" footer shortcut when `currentUserId` is set. Default true. |
size | "sm" | "md" | "lg" | |
sortByMatch | boolean | Rank matches whose label starts with the query first. Default true. |
trigger | ReactElement<unknown, string | import("/home/runner/work/monarch/monarch/node_modules/@types/react/index").JSXElementConstructor<any>> | A custom trigger element (a chip, an icon button). It must accept a ref and name itself, for example `aria-label="Status: Open"`. Default: a field-style button. |
value | string | null | Selected record id (controlled). Single-select only. |
valuePeople | AssigneePerson[] | People for the current value(s) when they may not be in `people`. Used for names and avatars. |
values | string[] |
Variants
| Prop | Values | Description |
|---|---|---|
multiple | 'false''true' | One assignee with avatar and name, or several as an avatar stack and a count. |
size | 'sm''md''lg' | Trigger height, as EntityPicker. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Unassigned | data-placeholder | No one assigned: "Unassigned" in muted text. |
| Assigned | – | The trigger shows the avatar and name ("(you)" for the current user). |
| Several | – | An AvatarGroup (up to maxAvatars, then +N) and "N people". |
| Open | aria-expanded="true" | The people list with search. |
| Invalid / disabled | data-invalid, disabled | From invalid, disabled or the Field. |
Interaction behaviour
- Search matches name and email.
- The current user (
currentUserId) is listed first as "Name (you)"; "Assign to me" in the footer assigns them in one click. - "Unassigned" (on by default,
allowUnassigned={false}to hide) clears the assignment. - Single closes after a pick; multiple toggles people and stays open.
- With
onSearch, people are searched on the server with debounce and abort, as in Combobox.
Keyboard support
| Keys | Action |
|---|---|
| Enter / Space / ArrowDown (trigger) | Opens the picker. |
| ArrowDown / ArrowUp, Home / End | Moves through people. |
| Enter | Assigns (or toggles) the active person. |
| Tab | Reaches "Assign to me" in the footer. |
| Escape | Closes and returns focus to the trigger. |
Accessibility
- Role
- select-only combobox opening a dialog with a search combobox and listbox
- Accessible name
- The Field label or
aria-label; the current assignee is the trigger’s content.
- Avatars in rows and the trigger are decorative; the name (and "(you)") carries the meaning.
- Presence is shown on row avatars; give status in
hinttoo when it matters, as colour alone is not enough. - Inherits EntityPicker’s dialog, combobox and listbox semantics.
Responsive behaviour
- The list is at least 16rem wide; names and emails truncate.
- The trigger keeps the avatar stack and truncates the text.
Theme support
- Avatars fall back to initials on seeded colours; everything else uses semantic tokens in all themes.
Do and don't
Do
- Pass
currentUserIdso people can assign themselves quickly. - Group people the way the product thinks ("In this team", "Everyone else") with
group. - Pass
valuePeoplewhen the assignee may not be in the loaded list.
Don't
- Do not use a native select of members; it loses search and avatars.
- Do not put the person’s availability only in the presence dot colour.
Related components
- EntityPicker: The generic record picker it is built on.
- Avatar: The avatar and avatar stack.
- Combobox: Input-style search field.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | Avatar, EntityPicker |
Testing coverage
- Lists Unassigned, then you, then everyone with avatars; search matches email; picks and updates the trigger.
- "Assign to me" assigns and disappears once assigned; Unassigned clears.
- Multiple stays open and summarises as an avatar stack with a count.
- Server search keeps the picked name; valuePeople and translated messages.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/assignee-picker; Cross-browser axe: /components/assignee-picker |
Tests: packages/react/src/components/identity/assignee-picker