KQ-1310 · MEMBER/ROSTER
Member Roster
The people in a workspace and what each can do. A role changes only after a confirm unfolds under the row to its measured height on glide and is accepted — in menu mode a chip whose label then rolls to the new role on snap, in segmented mode a thumb that slides on snap and waits dashed until the confirm — and the last owner can never be demoted or removed. Invites are typed or pasted as email chips that land one after another from 0.6 scale on snap, pending invites resend behind a cooldown bar that drains linearly, and removing someone folds the row away, tipping back about its top edge while its height runs to zero and the rows below close the gap. Menus and radio groups take the arrow keys, confirms take focus when they arrive and give it back on Escape.
Members of Basinworks
Basinworks · 7 people
7 members · 2 pending · 9 of 10 seats
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/member-roster
Props
- members / defaultMembers
- RosterMember[]
- default defaultRosterMembers
- Everyone in the workspace: name, email, role, last active and whether it is you. Controlled or uncontrolled.
- onMembersChange
- (members: RosterMember[]) => void
- default —
- Fires from the confirm that changed a role or removed someone, with every member.
- invites / defaultInvites
- RosterInvite[]
- default defaultRosterInvites
- Invites still waiting: email, role and when they were sent. Controlled or uncontrolled.
- onInvitesChange
- (invites: RosterInvite[]) => void
- default —
- Fires when invites are sent or revoked, with every pending invite.
- roleSet
- RosterRole[]
- default defaultRosterRoles
- The roles a member can hold, most powerful first, each with a sentence of what it can do. A role with id "owner" is never left empty.
- now
- Date | number
- default defaultRosterNow
- The moment “last active” and “sent” are dated against; the component never reads the clock in render.
- seats
- number
- default 10
- How many seats the plan has. Members and pending invites both take one; inviting past it is refused.
- cooldown
- number
- default 30
- Seconds before an invite can be sent again.
- workspace
- string
- default "Basinworks"
- The workspace's name, for the heading and the confirms.
- onRoleChange
- (memberId: string, role: string, previous: string) => void
- default —
- A role change was confirmed.
- onRemove
- (memberId: string) => void
- default —
- A removal was confirmed.
- onInvite
- (emails: string[], role: string) => void
- default —
- Invites were sent, all with one role.
- onResend
- (inviteId: string) => void
- default —
- An invite was sent again; its cooldown starts.
- onRevoke
- (inviteId: string) => void
- default —
- An invite was withdrawn.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Row height, avatar size and type size.
- invite
- "inline" | "drawer" | "off"
- default "inline"
- The invite field: always shown, behind an Invite button that glides it open, or not offered.
- roles
- "menu" | "segmented"
- default "menu"
- How a role is changed: a menu of roles with what each can do, or a segmented control in the row (a menu on a phone). Both confirm first.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholder rows; error offers Retry.
- onRetry
- () => void
- default —
- The Retry button of the error state.
- label
- string
- default —
- The region's accessible name. Defaults to “Members of” and the workspace.
- sound
- boolean
- default false
- Play a click on chips, role menus, resends, revokes and removals and a pop on confirmed roles and sent invites. Off unless asked for.
- disabled
- boolean
- default false
- Shows the roster dimmed and changes nothing.
- className
- string
- default —
- Classes for the root. The root is h-[560px] by default; pass a height to change it.
| Prop | Type | Default | Description |
|---|---|---|---|
| members / defaultMembers | RosterMember[] | defaultRosterMembers | Everyone in the workspace: name, email, role, last active and whether it is you. Controlled or uncontrolled. |
| onMembersChange | (members: RosterMember[]) => void | — | Fires from the confirm that changed a role or removed someone, with every member. |
| invites / defaultInvites | RosterInvite[] | defaultRosterInvites | Invites still waiting: email, role and when they were sent. Controlled or uncontrolled. |
| onInvitesChange | (invites: RosterInvite[]) => void | — | Fires when invites are sent or revoked, with every pending invite. |
| roleSet | RosterRole[] | defaultRosterRoles | The roles a member can hold, most powerful first, each with a sentence of what it can do. A role with id "owner" is never left empty. |
| now | Date | number | defaultRosterNow | The moment “last active” and “sent” are dated against; the component never reads the clock in render. |
| seats | number | 10 | How many seats the plan has. Members and pending invites both take one; inviting past it is refused. |
| cooldown | number | 30 | Seconds before an invite can be sent again. |
| workspace | string | "Basinworks" | The workspace's name, for the heading and the confirms. |
| onRoleChange | (memberId: string, role: string, previous: string) => void | — | A role change was confirmed. |
| onRemove | (memberId: string) => void | — | A removal was confirmed. |
| onInvite | (emails: string[], role: string) => void | — | Invites were sent, all with one role. |
| onResend | (inviteId: string) => void | — | An invite was sent again; its cooldown starts. |
| onRevoke | (inviteId: string) => void | — | An invite was withdrawn. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Row height, avatar size and type size. |
| invite | "inline" | "drawer" | "off" | "inline" | The invite field: always shown, behind an Invite button that glides it open, or not offered. |
| roles | "menu" | "segmented" | "menu" | How a role is changed: a menu of roles with what each can do, or a segmented control in the row (a menu on a phone). Both confirm first. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholder rows; error offers Retry. |
| onRetry | () => void | — | The Retry button of the error state. |
| label | string | — | The region's accessible name. Defaults to “Members of” and the workspace. |
| sound | boolean | false | Play a click on chips, role menus, resends, revokes and removals and a pop on confirmed roles and sent invites. Off unless asked for. |
| disabled | boolean | false | Shows the roster dimmed and changes nothing. |
| className | string | — | Classes for the root. The root is h-[560px] by default; pass a height to change it. |