## Team Members (S—029)

Team management with email invites, seat limits, role changes, pending invites and safe removal.

- URL: https://sundaeui.dev/components/team-members
- Category: UI components · Art direction: Understated
- Access: Sundae Pro (authenticated install)
- Stack: React 18+, TypeScript, Tailwind CSS; dependencies: none
- Install: `npx shadcn@latest add https://sundaeui.dev/r/team-members.json`
- Tags: dashboard, tailwind, form, saas, permissions, settings, roles, invite, team, react

The settings screen every B2B product needs, with the edge cases handled. Invites validate the email, reject duplicates and stop when the plan’s seats are full, with a seat meter in the header. Pending invites are labelled and can be resent or revoked.
Roles change inline. Removing someone asks for confirmation in place rather than in a modal, and the last owner can’t be removed, with a tooltip explaining why. Status messages confirm each action without interrupting.
Use it in workspace or organisation settings. Wire onInvite to your API. State is controlled, so the list only changes when you say so.

### Usage

```tsx
import { TeamMembers } from '@/components/sundae/team-members'

<TeamMembers members={members} onChange={setMembers} seats={plan.seats} onInvite={(email, role) => api.invite({ email, role })} />
```

### Props

- `members` (`Member[]`, required): { id, name, email, role, pending? }.
- `onChange` (`(members) => void`, required): Called after role changes, invites and removals.
- `seats` (`number`): Seat limit. Shows a meter and blocks invites when full.
- `onInvite` (`(email, role) => Promise<void>`): Persists an invite. Throw to show an error.

### Accessibility

- Every control is labelled, including per-member role selects and remove buttons.
- Invite errors and confirmations are announced through a status region.
- Removal confirms in place, and focus stays in the row.
