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.

KQ-1310 · MEMBER/ROSTER

Members of Basinworks

Basinworks · 7 people

9 of 10 seats

Invite people

1 seat left.

MemberRoleLast activeActions

Pending invites· 2

  • rafael@basinworks.io

    Member · sent 2 days ago

  • jun@gaugeworks.io

    Viewer · sent 5 hr ago

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.