Components · Identity
MemberRow
One member of a workspace or space: avatar, name, email, role, status (invited, active, suspended) and confirmed actions.
Members with editable roles
Priya Raman(You)
OwnerJonas Weber
Amara Okafor
import { useState } from 'react';
import { MemberList, MemberRow } from '@rookcom/monarch-react/patterns';
const ROLES = [
{ value: 'owner', label: 'Owner' },
{ value: 'admin', label: 'Admin' },
{ value: 'member', label: 'Member' },
];
const wait = () => new Promise((r) => setTimeout(r, 700));
function TeamDemo() {
const [people, setPeople] = useState([
{ id: '1', name: 'Priya Raman', email: '[email protected]', role: 'owner', self: true },
{ id: '2', name: 'Jonas Weber', email: '[email protected]', role: 'admin' },
{ id: '3', name: 'Amara Okafor', email: '[email protected]', role: 'member' },
]);
return (
<MemberList aria-label="Workspace members" style={{ width: '100%' }}>
{people.map((p) => (
<MemberRow
key={p.id}
name={p.name}
email={p.email}
isSelf={p.self ?? false}
roleOptions={ROLES}
memberRole={p.role}
onMemberRoleChange={async (role) => {
await wait();
setPeople((all) => all.map((x) => (x.id === p.id ? { ...x, role } : x)));
}}
onRemove={async () => {
await wait();
setPeople((all) => all.filter((x) => x.id !== p.id));
}}
/>
))}
</MemberList>
);
}
<TeamDemo />Usage examples
Invited, suspended and read-only
import { Badge } from '@rookcom/monarch-react';
import { MemberList, MemberRow } from '@rookcom/monarch-react/patterns';
const ROLES = [
{ value: 'owner', label: 'Owner' },
{ value: 'admin', label: 'Admin' },
{ value: 'member', label: 'Member' },
];
const wait = () => new Promise((r) => setTimeout(r, 700));
<MemberList aria-label="People" style={{ width: '100%' }}>
<MemberRow
name="[email protected]"
status="invited"
meta="Invite expires in 6 days"
roleOptions={ROLES}
memberRole="member"
onResend={wait}
onRemove={wait}
/>
<MemberRow
name="Sam Carter"
email="[email protected]"
status="suspended"
roleOptions={ROLES}
memberRole="member"
/>
<MemberRow
name="Mei Tanaka"
email="[email protected]"
presence="online"
badges={<Badge size="sm">Guest</Badge>}
meta="Joined 12 March"
roleOptions={ROLES}
memberRole="admin"
/>
</MemberList>Purpose
The same member row everywhere people are listed with a role: workspace and space members, channel members, project and team members. The product passes the person, the roles allowed and callbacks; who may change what stays in the product. Import from @rookcom/monarch-react/patterns.
Use when
- A list of people with a role that some viewers may change.
- Pending invitations shown alongside members, with resend and revoke.
Avoid when
- Choosing people to add: use EntityPicker or AssigneePicker.
- Dense tables with many columns per person: use DataTable with Avatar cells.
Design principles
- Identity first: avatar, name and email read as one unit; everything else is secondary.
- Only what differs is marked: invited and suspended get a badge, active members do not, so a long list stays quiet.
- Every control is named after the person (“Role for Jonas Weber”, “Remove Jonas Weber”), so a list of identical buttons is still clear to screen-reader users.
- Destructive actions are confirmed in a ConfirmDialog that can show what will happen.
- Async by default: role changes and resends show a busy state; failures are explained in the row and roll back.
API reference
import { MemberList, MemberRow } from '@rookcom/monarch-react/patterns';| Export | Kind | Rendering | Root class |
|---|---|---|---|
MemberList | component | client | .mn-member-row |
MemberRow | component | client | .mn-member-row |
MemberRow renders an <li> (ref) by default, or a <div> with as="div"; MemberList is the <ul> that holds rows.
The member’s role is memberRole / defaultMemberRole / onMemberRoleChange (not the ARIA role), chosen from roleOptions.
onMemberRoleChange, onResend and onRemove may return promises; a rejected role change restores the previous role.
Data attributes: data-status (invited, active, suspended) and data-self.
Generated from the TypeScript declarations of packages/react/src/components/identity/member-row.
Properties
MemberList
The list that holds MemberRows, so screen readers announce "list, 12 items".
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
className | string | |
id | string |
MemberRow
One member of a workspace, space or channel: avatar, name, email, role, status (invited, active, suspended) and actions. Role changes, resends and removals go through product callbacks; removal is confirmed first. Use inside `MemberList`.
| Prop | Type | Description |
|---|---|---|
name required | string | Display name. Also seeds the avatar initials and colour. |
actions | ReactNode | More trailing actions, such as a DropdownMenu. Name them after the member. |
as | "div" | "li" | Element to render. Default `li`, for use inside `MemberList`. |
avatarUrl | string | null | Avatar image URL; initials are shown without it or when it fails. |
badges | ReactNode | Extra markers after the name, such as a Guest badge. |
canChangeRole | boolean | Show the role select. Default: when `onMemberRoleChange` is given and it is not your own row. |
canRemove | boolean | Show Remove. Default: when `onRemove` is given and it is not your own row. |
className | string | |
defaultMemberRole | string | Initial member role (uncontrolled). |
disabled | boolean | Disable every control in the row. |
email | ReactNode | Email or handle, shown under the name. |
id | string | |
isSelf | boolean | The row is the signed-in person: shows "You" and hides Remove. |
memberRole | string | The member's role (controlled), one of `roleOptions`' values. Not the ARIA `role`. |
messages | Partial<MemberRowMessages> | Override visible and announced strings. |
meta | ReactNode | A line of extra detail under the email: joined date, invite expiry, availability. |
onMemberRoleChange | ((role: string) => unknown) | Makes the role a select. Return a promise to show it busy; reject to restore the previous role and show the error in the row. |
onRemove | (() => unknown) | Remove the member (or revoke the invitation), after a confirmation dialog. Return a promise to keep the dialog pending; reject to show the error there. |
onResend | (() => unknown) | Resend an invitation. Shown for `status="invited"`. |
presence | AvatarStatus | Presence dot on the avatar. |
removeConsequences | ReactNode[] | Consequences listed in the remove dialog ("3 assigned tickets go back to the queue"). |
roleOptions | readonly NativeSelectOption[] | The roles the product allows here. Their labels also show for read-only roles. |
status | MemberStatus | Default `active`. `invited` and `suspended` show a badge and change the actions. |
Variants
| Prop | Values | Description |
|---|---|---|
status | 'active''invited''suspended' | invited shows an Invited badge, Resend and Revoke; suspended a Suspended badge and a muted name. |
as | 'li''div' | li (default) inside MemberList; div for a single row in a card or popover. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Active | data-status="active" | No status badge. |
| Invited | data-status="invited" | Info badge; Resend invite; Remove becomes Revoke invite. |
| Suspended | data-status="suspended" | Warning badge and a muted name. |
| Self | data-self | “(You)” after the name; role select and Remove hidden by default. |
| Role read-only | – | Without onMemberRoleChange (or with canChangeRole={false}) the role label is text. |
| Busy | aria-busy on the select or button | While a role change or resend runs. |
| Error | role="alert" in the row | A failed role change or resend, in the product’s words. |
| Disabled | – | Every control in the row disabled. |
Interaction behaviour
- Changing the role calls
onMemberRoleChange. Uncontrolled, the select updates at once and rolls back if the promise rejects; controlled, it waits for the product’s newmemberRole. - Resend calls
onResendand announces “Invite sent again.” - Remove opens a danger ConfirmDialog (title, description and optional consequences) and calls
onRemove; errors stay in the dialog so the person can retry. - Extra actions (for example a DropdownMenu) go in
actions, before Remove.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Moves between the role select, Resend, extra actions and Remove. |
| Arrow keys | Change the role in the native select. |
| Enter / Space | Activates Resend or Remove. |
| Escape | Closes the remove confirmation. |
Accessibility
- Role
- listitem (in MemberList, a list)
- Accessible name
- Rows are named by their text; controls are labelled after the member.
- MemberList is a real
<ul>, so screen readers announce “list, n items”. - The avatar is decorative next to the visible name; presence is read as hidden text after the name (“Online”).
- Role select: “Role for {name}”. Remove: “Remove {name}” or “Revoke invite for {name}”. Resend: “Resend invite, {name}”.
- Resend success is a polite status; failures are
role="alert". - Verified with axe in jsdom and in Chromium, Firefox and WebKit on /components/member-row.
Responsive behaviour
- Name and email truncate with an ellipsis instead of pushing the controls off screen.
- When the row is narrower than 26rem (container query), the controls move under the name, aligned with the text.
- Buttons keep their touch size at every width.
Theme support
- Monarch tokens only: names in
--mn-color-text-default, emails in--mn-color-text-muted, row separators--mn-color-border-subtle. - Badges use the info and warning tones; follows light, dark and high-contrast themes.
Do and don't
Do
- Pass only the roles the viewer may assign in
roleOptions; enforce the same rule on the server. - Add
removeConsequenceswhen removing someone affects their work (“3 open tickets go back to the queue”). - Use
metafor one useful fact: joined date, invite expiry or availability.
Don't
- Do not let people remove themselves from this row; offer “Leave” elsewhere.
- Do not put the role in a menu when there are only a few roles; the native select is faster.
- Do not show “Active” on every row; it is the default.
Related components
- Avatar: The avatar used here; on its own for compact people chips.
- AssigneePicker: Choose people to assign or add.
- DataTable: Many columns per person, sorting and bulk actions.
- ConfirmDialog: The removal confirmation.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | Button, Badge, Field, ConfirmDialog, Avatar |
Testing coverage
- Identity, presence text, badges, email, meta and read-only role in a list; self row; axe.
- Labelled role select changes the role (uncontrolled); axe.
- A rejected role change restores the previous role and shows the error.
- Controlled role waits for the product and is busy meanwhile.
- Own row hides role select and Remove by default.
- Resend calls
onResendand announces success; axe. - Remove by keyboard opens a confirm dialog with consequences, focus on Cancel; rejection shown in the dialog, retry succeeds.
- Invited rows revoke; suspended rows are marked.
- Disabled,
as="div"and translated messages; renders on the server.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/member-row; Cross-browser axe: /components/member-row |
Tests: packages/react/src/components/identity/member-row