Skip to content
Component installs need the registry setup
VegaStack Design

Member List

The people in a team, a space or a share list — an add row (picker · role · Add) and 36px rows with a role chip and a Remove menu that scroll after eight.

Status
stable
Since
0.25.87
Accessibility pattern
named list; role and ⋯ menus named per row

Last updated

  • MKManoj Kumar (you)Owner
  • Product team2 people
  • SDSanjay Desai
  • PSPriya Shah

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Member List before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify member-list

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/member-list

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

The same command installs the registry items it composes: @vegastack/button, @vegastack/dropdown-menu, @vegastack/permission-menu, @vegastack/person-avatar, @vegastack/scroll-area, @vegastack/skeleton.

Usage

import { MemberList, MemberListAdd } from "@/components/ui/member-list";
import { PeoplePicker } from "@/components/ui/people-picker";

<MemberList
  add={
    <MemberListAdd
      picker={
        <PeoplePicker
          multiple
          value={chosen}
          onValueChange={setChosen}
          search={searchPeople}
        />
      }
      roles={ROLES}
      role={role}
      onRoleChange={setRole}
      onAdd={addChosen}
      disabled={chosen.length === 0}
    />
  }
  members={members}
  roles={ROLES}
  onRoleChange={changeRole}
  onRemove={removeMember}
/>;

One list for every "people in X": a team's members, a space's members, who an item is shared with, an invite list. MemberListAdd on top is the picker, an optional role for the new members (a bordered PermissionMenu that lines up with the picker) and Add. Each row is 36px: the PersonAvatar, the name over a muted sub line ("2 people" for a team, "Creator"), the role as a chip menu, and a ⋯ menu with Remove. After maxRows (eight) the list scrolls inside itself. Data-agnostic: rows, roles and every string come in through props and every change goes out through a callback. ShareDialog is built on it.

Scope

  • Owns: the add row's layout, the rows, the role chip and ⋯ menus, locked rows, the loading and empty states, the scroll cap.
  • Does not own: searching people (the picker is the host's), what a role means, or saving.
  • Compose with: PeoplePicker in the add row; a DropdownMenuItem per extra row action through menuItems.

Anatomy

MemberList — data-slot="member-list" | "member-list-description" | "member-list-empty" | "member-list-more" | "member-list-more-spacer" | "member-list-name" | "member-list-role" | "member-list-row" | "member-list-rows" | "member-list-scroll"
MemberListAdd — data-slot="member-list-add" | "member-list-add-button" | "member-list-add-picker"

Examples

Add, change, remove

A built-in row (the owner, the creator) is locked: its role shows with a lock and lockedReason in a tooltip, and it has no ⋯ — but the ⋯ column keeps its width on every row, so the role chips line up. you adds "(you)" after the viewer's name. Narrower than 28rem, the add row puts the role and Add under the picker.

  • MKManoj Kumar (you)Owner
  • Product team2 people
  • SDSanjay Desai
  • PSPriya Shah

Read-only

canManage={false} shows every role as text and drops the menus. Without roles, a row's role is plain text — a team's "Lead" and "Member", say.

  • MKManoj Kumar (you)OwnerFull access
  • Product team2 peopleFull access
  • SDSanjay DesaiCan edit
  • PSPriya ShahCan view
  • SDSanjay DesaiLead
  • PSPriya ShahMember

Long lists and more actions

Past maxRows the rows scroll inside the list (a whole number of 36px rows tall). menuItems adds the host's own items to a row's ⋯ menu, above Remove — "Resend invite" for an invited person; removable: false keeps a row's menu without Remove.

API Reference

MemberList

PropTypeDefaultDescription
members*readonly MemberListMember[]—The rows, in order.
addReact.ReactNode—The add row above the list — a MemberListAdd.
canManagebooleantrueThe viewer may change the list: role menus and ⋯ menus show. Without it, roles are text and there are no menus.
emptyReact.ReactNodelabels.empty, mutedWhat shows with no rows; null shows nothing.
labelsPartial<MemberListLabels>{}Override any rendered string.
loadingbooleanfalseRows are loading: skeleton rows show.
maxRowsnumber8Rows shown before the list scrolls inside itself.
menuItems((member: MemberListMember) => React.ReactNode)—More items for a row's ⋯ menu, above Remove — DropdownMenuItems ("Resend invite").
onRemove((id: string) => void)—Removes one row; adds Remove to its ⋯ menu.
onRoleChange((id: string, role: string) => void)—Changes one row's role; without it every role reads as text.
rolesreadonly PermissionMenuOption<string>[]—The roles a row can have, most access first. With them each row's role is a chip menu; without them a row's role is plain text.

Data attributes and CSS variables on MemberList

AttributeValues
data-slot"member-list" | "member-list-description" | "member-list-empty" | "member-list-more" | "member-list-more-spacer" | "member-list-name" | "member-list-role" | "member-list-row" | "member-list-rows" | "member-list-scroll"

Each row of members:

PropTypeDefaultDescription
id*string—A stable id — the person's or the team's.
person*Person—The person, team (kind: "team") or agent.
descriptionReact.ReactNode—The muted sub line under the name — "2 people" for a team, "Creator", "via Sales team".
lockedbooleanfalseA built-in row (the creator, an owner): the role shows with a lock and lockedReason in a tooltip, and the row has no ⋯ menu.
lockedReasonReact.ReactNode—Why a locked row cannot change, in its tooltip — "Set by role: Creator".
removablebooleantrueOffer Remove on this row (when the list has onRemove).
rolestring—Their role — one of the list's roles, or plain text when the list has none.
roleReadOnlybooleanfalseShow this row's role as text, with no menu, even for a manager.
youbooleanfalseThis row is the viewer: "(you)" follows the name.

MemberListAdd

PropTypeDefaultDescription
picker*React.ReactNode—The picker that chooses who to add — a PeoplePicker, say. It fills the row.
addingbooleanfalseAn add is in flight: Add spins, and the role control waits.
addLabelstring"Add"The Add button's text.
disabledbooleanfalseNothing to add yet (no one chosen): Add is disabled.
onAdd(() => void)—Adds the chosen people.
onRoleChange((role: string) => void)—Changes the role for the new members; without it the role reads as text.
rolestringthe last of `roles`The role the new members get — one of roles.
roleLabel((role: string) => string)(role) => `Role for new members: ${role}`The role control's accessible name, with the visible role in it.
rolesreadonly PermissionMenuOption<string>[]—The roles the new members can get. Omit for no role control.

Data attributes and CSS variables on MemberListAdd

AttributeValues
data-slot"member-list-add" | "member-list-add-button" | "member-list-add-picker"

Accessibility

  • The rows are a list named by labels.list ("Members"); a loading list is aria-busy.
  • Every role menu is named for its row with the visible role in it ("Priya Shah's role: Can view"), and every ⋯ button for its row ("More for Priya Shah").
  • A locked role is still a tab stop, with its reason read out and shown in a tooltip.
  • The sub line is its own line of text, read after the name.
KeyAction
TabMoves through the add row, then each row's menus
Enter / SpaceOpens a role or ⋯ menu
ArrowUp / ArrowDownMoves through an open menu
EscapeCloses the open menu
ContractStates tested
Behaviourdefault, loading, empty, locked, read-only, scrolling, adding
Accessibilitylabeled, keyboard, browser-accessibility-test
Visualdefault, hover, focus, open, locked, read-only

Do / Don't

Do
Use one MemberList wherever people belong to something — teams, spaces, shares, invites — so adding, changing a role and removing work the same everywhere.
Don't
Draw members as bordered cards with a text Remove button, or put the picker and Add on separate rows.

On this page