| Field | Value |
|---|
| ID | FRD-045 |
| Owner | David Holmes |
| Status | Draft |
| Priority | P1 — Recipes |
| Size | M (Recipe) |
| Target Release | v2.0.0 |
| Last Updated | 2026-05-26 |
| Related | ADR-027 (Default Tech Stack) |
Document Summary
Create an MDX recipe in Storybook that teaches developers how to compose a Team Management page — the universal B2B admin surface for managing organization members. The recipe covers a member list with role display and actions, role change flows, pending invite management (resend/revoke), seat count tracking, and comprehensive loading/empty/error states. It provides copy-pasteable code for a realistic team administration experience.
Introduction
Overview
Every B2B SaaS product needs a team management page where admins can view members, change roles, invite new members, and manage pending invitations. This pattern recurs across virtually every multi-user application, yet developers rebuild it from scratch each time. The recipe codifies the canonical composition of design system components for this surface, including the tricky edge cases around invite states, seat limits, and role hierarchies.
Goals
- Provide a single MDX recipe showing the complete Team Management page composition.
- Show a member list using DataGrid with role badges, status indicators, and row actions (change role, remove).
- Show an invite flow with email input, role selection, and confirmation.
- Show pending invite management with resend and revoke actions.
- Show seat count tracking with upgrade CTA when at limit.
- Cover all states: loading (skeleton), empty (no members besides self), error (failed to load), pending invites section.
Non-Goals
- Implementing RBAC (role-based access control) logic — the recipe shows the UI, not the authorization layer.
- SSO/SAML integration details.
- Audit log of team changes (separate pattern).
- Multi-team or organization hierarchy (separate, more complex pattern).
Scope
In Scope
| Item | Description |
|---|
| MDX recipe page | src/docs/recipes/team-management.mdx with Storybook sidebar entry |
| Member list | DataGrid showing name, email, role badge, status, last active, row actions |
| Invite flow | Dialog with email input (multi-email support), role selector, send button |
| Pending invites | Separate section or tab showing invited-but-not-accepted members with resend/revoke |
| Role change | DropdownMenu or Select for changing a member’s role with confirmation |
| Remove member | AlertDialog confirmation for removing a team member |
| Seat count | Display of “X of Y seats used” with progress indicator and upgrade CTA |
| State coverage | Loading skeletons, empty state, error state for the member list |
| Overview page update | Add Team Management recipe to src/docs/recipes/00-overview.mdx |
Out of Scope
| Item | Reason |
|---|
| Authorization middleware | Backend concern; recipe shows the UI composition |
| Email delivery for invitations | Backend service; recipe shows the UI trigger |
| SSO/SAML configuration | Complex identity provider integration, separate pattern |
| Organization switching | Multi-org support is a different page pattern |
Users and Pain Points
| User | Pain Point |
|---|
| Application developer | No canonical pattern for member list + invite flow + pending invites composition |
| Application developer | Unclear how to handle seat limits in the UI — when to block invites vs. show upgrade |
| Application developer | Role change confirmation UX is inconsistent — some apps use inline, others use dialogs |
| Application developer | Pending invite states (sent, expired, revoked) need distinct visual treatment but no guide exists |
| Product manager | Inconsistent team management UX across products |
Definitions
| Term | Definition |
|---|
| Seat | A billable unit representing one team member slot in the subscription plan |
| Pending invite | An invitation that has been sent but not yet accepted by the recipient |
| Role | A permission level assigned to a team member (e.g., Owner, Admin, Member, Viewer) |
| Resend | Re-sending an invitation email to a pending invitee |
| Revoke | Canceling a pending invitation before it is accepted |
Current State
- No team management recipe exists in
src/docs/recipes/.
- Components available for composition: DataGrid, Dialog, AlertDialog, DropdownMenu, Select, TextField, Badge, Button, Toast, EmptyState, Skeleton, ProgressBar, Avatar.
- The recipes overview does not list a team management recipe (it will need to be added).
- Common patterns for team management exist across the industry but are not documented in this design system.
Proposed Solution
Create src/docs/recipes/team-management.mdx with the following structure:
- Introduction — What this recipe builds, when to use it, when not to use it.
- Component inventory — Table listing all components with links.
- Data model — TypeScript interfaces for
TeamMember, PendingInvite, and TeamSeatInfo.
- Page layout — Overall structure with seat count header, member list, and pending invites section.
- Seat count header — Current seats used, total seats, progress indicator, upgrade CTA when at limit.
- Member list — DataGrid with columns: Avatar + Name, Email, Role (badge), Status, Last Active, Actions (dropdown menu).
- Invite dialog — Multi-email input (TokenInput or comma-separated TextField), role selector, send button, validation.
- Role change — DropdownMenu trigger on role badge, confirmation dialog for privilege escalation/demotion.
- Remove member — AlertDialog with member name, impact warning, confirm/cancel.
- Pending invites — List or table showing pending invites with status (Sent, Expired), sent date, resend/revoke actions.
- States — Loading skeletons for the member grid, empty state (new team with only the owner), error state (failed to fetch members).
- Full composition — Complete code block.
Requirements
| ID | Requirement | Priority |
|---|
| REQ-01 | Recipe is a single MDX file in src/docs/recipes/ | Must |
| REQ-02 | Recipe shows member list with role badges and row actions | Must |
| REQ-03 | Recipe shows invite dialog with email input and role selection | Must |
| REQ-04 | Recipe shows pending invite management with resend/revoke | Must |
| REQ-05 | Recipe shows seat count tracking | Must |
| REQ-06 | Recipe shows loading, empty, and error states | Must |
| REQ-07 | All code blocks are copy-pasteable and self-contained | Must |
| REQ-08 | Recipe shows role change with confirmation for privilege changes | Should |
| REQ-09 | Recipe shows remove member with AlertDialog confirmation | Should |
Functional Requirements
| ID | Description | Acceptance |
|---|
| FR-01 | MDX file renders in Storybook without errors | pnpm build-storybook succeeds |
| FR-02 | All code blocks compile when extracted | Manual verification |
| FR-03 | Invite dialog validates email format | Code example includes validation |
| FR-04 | Seat count prevents inviting when at limit | Code example disables invite button at seat limit |
| FR-05 | Pending invite section distinguishes sent vs. expired invites | Badge or status indicator differentiates |
Non-Functional Requirements
| ID | Description | Target |
|---|
| NFR-01 | Recipe page load time | Under 2 seconds |
| NFR-02 | Individual code block line count | Under 80 lines per block |
| NFR-03 | Full composition line count | Under 250 lines |
API/Interface Requirements
| Interface | Requirement |
|---|
| MDX file | Must use “ |
TeamMember interface | id, name, email, avatarUrl, role, status, lastActiveAt |
PendingInvite interface | id, email, role, sentAt, expiresAt, status |
TeamSeatInfo interface | used, total, planName |
Accessibility Requirements
| ID | Requirement |
|---|
| A11Y-01 | Member list must be navigable by keyboard with clear focus indicators |
| A11Y-02 | Role change dropdown must announce the current role and available options |
| A11Y-03 | Invite dialog email input must have an accessible label and error announcements |
| A11Y-04 | Remove member dialog must include the member’s name in its accessible description |
| A11Y-05 | Seat count must be announced as "{used} of {total} seats used" for screen readers |
| A11Y-06 | Empty and error states must use appropriate ARIA roles |
Content and Documentation Requirements
| ID | Requirement |
|---|
| DOC-01 | Update src/docs/recipes/00-overview.mdx to add Team Management recipe |
| DOC-02 | Include a “Role hierarchy” callout explaining Owner > Admin > Member > Viewer |
| DOC-03 | Include a “When to use” / “When not to use” section |
Dependencies
| Dependency | Type | Risk |
|---|
| DataGrid component | Internal | Must support row actions and custom cell renderers |
| Dialog component | Internal | Used for invite flow |
| AlertDialog component | Internal | Used for remove member confirmation |
| DropdownMenu component | Internal | Used for role change and row actions |
| Badge component | Internal | Used for role and status display |
| TokenInput component | Internal | Preferred for multi-email input; falls back to TextField |
| Avatar component | Internal | Used in member list name column |
Risks and Tradeoffs
| Risk | Impact | Mitigation |
|---|
| TokenInput may not exist for multi-email input | Less polished invite UX | Fall back to comma-separated TextField with validation |
| Role hierarchy varies by application | Recipe may not fit all role models | Use a generic 4-tier hierarchy (Owner, Admin, Member, Viewer); note that apps should customize |
| Seat limit logic interacts with billing | Cross-concern complexity | Recipe shows the UI only; link to the Billing recipe for upgrade flows |
Open Questions
| # | Question | Status |
|---|
| 1 | Should pending invites be a separate tab or a section below the member list? | Open — leaning section |
| 2 | Should the recipe show a “Transfer ownership” flow? | Open — likely out of scope for v1 |
| 3 | Should expired invites be automatically hidden or shown with a “Resend” CTA? | Open |
| 4 | Should the recipe include a “Bulk invite from CSV” pattern? | Open — likely a follow-up |
Acceptance Criteria
LLM Handoff Instructions
When an LLM agent picks up this FRD:
- Read
src/docs/recipes/00-overview.mdx for recipe conventions.
- Read component source for: DataGrid, Dialog, AlertDialog, DropdownMenu, Select, TextField, Badge, Button, Toast, EmptyState, Skeleton, Avatar, TokenInput (if it exists).
- Read each component’s stories to understand usage patterns.
- Create
src/docs/recipes/team-management.mdx with the section structure from the Proposed Solution.
- Sample data: A team of 6 members across 4 roles (1 Owner, 1 Admin, 3 Members, 1 Viewer), 2 pending invites (1 sent yesterday, 1 expired), seat plan of 8/10 seats used.
- Include TypeScript interfaces for
TeamMember, PendingInvite, and TeamSeatInfo.
- All code blocks must use public package import paths.
- After creating, run
pnpm build-storybook to verify.
- Update
src/docs/recipes/00-overview.mdx.
Decision Log
| Date | Decision | Rationale |
|---|
| 2026-05-26 | Use a 4-tier role hierarchy (Owner, Admin, Member, Viewer) | Covers the most common B2B role model; apps can customize |
| 2026-05-26 | Show pending invites as a section rather than a tab | Keeps the full team picture visible on one page; tabs hide information |
Document History
| Date | Version | Author | Changes |
|---|
| 2026-05-26 | 0.1 | David Holmes | Initial draft |