Skip to content

FRD-034: Invite Flow

FieldValue
OwnerDavid Holmes
StatusDraft
Last Updated2026-05-26
RelatedADR-027 (Default Tech Stack)
Target Releasev2.0.0
TypeWidget
SizeM
PriorityP1 — SaaS Widget

Document Summary

Build an Invite Flow widget providing an email invite form with role selection, a pending invite list with resend/revoke actions, and visual state management for accepted, expired, and pending invites. No invite-related widget currently exists in the design system.


Introduction

Overview

Team invitation flows are one of the most common SaaS patterns. Every multi-tenant application needs to invite users by email, assign a role, track invite status, and allow resending or revoking pending invites. The design system has no widget for this. Teams build it from scratch using TextField, Select, DataGrid, and Badge primitives, producing inconsistent implementations.

Goals

  • Provide an InviteFlow widget with an email invite form, role selector, and send action.
  • Display a list of pending, accepted, and expired invites with appropriate status badges.
  • Support resend and revoke actions on pending invites.
  • Handle multi-email input (comma-separated or one-at-a-time).
  • Validate email format client-side before sending.
  • Ship with Storybook stories and unit tests.

Non-Goals

  • Actual email sending (backend concern; widget calls callbacks).
  • Invite link generation or deep-link management.
  • Permission/RBAC management beyond role selection at invite time.

Scope

In Scope

ItemDescription
Invite formEmail input (single or multi), role selector, “Send Invite” button.
Pending invite listTable/list showing invitee email, role, status, sent date, and actions.
Status badgesVisual badges for Pending (yellow), Accepted (green), Expired (gray), Revoked (red).
Resend actionButton to resend a pending invite; calls onResend callback.
Revoke actionButton with confirmation to revoke a pending invite; calls onRevoke callback.
Email validationClient-side email format validation before sending.
Multi-email inputSupport comma-separated emails or a tag-input pattern.
State handlingLoading, empty (“No invites sent yet”), and error states.
Stories and testsFull Storybook coverage; unit tests for form validation and actions.

Out of Scope

ItemReason
Email deliveryBackend concern.
Invite link/token generationBackend concern.
Post-accept onboardingSeparate flow.
Bulk CSV importFuture enhancement; initial version supports manual entry.

Users and Pain Points

UserPain Point
SaaS developersBuild invite flows from scratch for every multi-tenant application.
Team adminsNo consistent UX for inviting members, checking invite status, or resending expired invites.
Product managersInvite flow inconsistencies lead to confusion about invite status across different products.
New usersUnclear whether their invite is pending, expired, or already accepted.

Definitions

TermDefinition
InviteAn email invitation to join a team/organization with a specific role.
PendingInvite sent but not yet accepted by the recipient.
AcceptedInvite accepted; the user has joined the team.
ExpiredInvite not accepted within the validity period.
RevokedInvite manually canceled by an admin before acceptance.
RoleThe permission level assigned to the invitee (e.g., “Admin”, “Member”, “Viewer”).

Current State

  • No invite-related widget exists in the design system.
  • TextField: Available for email input.
  • Select: Available for role selection.
  • DataGrid: Available for invite list display.
  • Badge: Available for status badges.
  • Button: Available for actions.
  • AlertDialog: Available for revoke confirmation.

Proposed Solution

Build src/components/widgets/invite-flow.tsx with two main sections:

Invite Form

A horizontal form row (or stacked on mobile) containing:

  1. Email input — A TextField with email validation. Supports multi-email via a tag-input variant: typed emails become removable tags. Validates each email on add.
  2. Role selector — A Select dropdown populated by consumer-provided roles.
  3. Send button — Calls onSendInvites({ emails, role }) callback. Disabled while sending (loading state on button).

Invite List

A table or styled list below the form showing all invites:

ColumnContent
EmailInvitee email address
RoleRole badge
StatusPending/Accepted/Expired/Revoked badge
SentRelative timestamp
ActionsResend (pending/expired), Revoke (pending)

Resend is available for pending and expired invites. Revoke opens an AlertDialog confirmation. Accepted and revoked invites show no actions (or a “Remove” action for accepted members, deferred to the Role Assignment Panel).

State Handling

  • Loading: Skeleton form and list.
  • Empty: “No invites sent yet. Invite your first team member above.”
  • Error: StatusCard with retry.

Requirements

Requirement Priorities

  • Must Have: Invite form with email + role, invite list with status badges, send/resend callbacks.
  • Should Have: Multi-email tag input, revoke with confirmation, expired state handling.
  • Could Have: Invite expiry countdown, CSV bulk import, custom invite message.

Functional Requirements

IDRequirementPriority
FR-01Invite form accepts email input, role selection, and a send button.Must
FR-02Email input validates format before allowing send.Must
FR-03Multi-email input supports comma-separated entry; each email becomes a removable tag.Should
FR-04Role selector is populated from consumer-provided roles array.Must
FR-05”Send Invite” button calls onSendInvites with emails and selected role.Must
FR-06Invite list displays email, role, status badge, sent date, and actions.Must
FR-07Status badges use semantic colors: Pending (warning), Accepted (success), Expired (muted), Revoked (destructive).Must
FR-08”Resend” action is available on pending and expired invites; calls onResend(inviteId).Should
FR-09”Revoke” action opens AlertDialog confirmation; calls onRevoke(inviteId).Should
FR-10Loading state shows skeleton form and list.Must
FR-11Empty state shows message with context.Must
FR-12Send button shows loading spinner while onSendInvites is in progress.Must

Non-Functional Requirements

IDRequirementTarget
NFR-01Email validation latency< 5ms per email (regex-based).
NFR-02Bundle size< 6 KB gzipped (excluding shared component deps).
NFR-03Dark modeFull token-based dark mode support.
NFR-04ResponsiveForm stacks vertically on screens < 640px.
NFR-05Max invite list sizePerformant with up to 200 invites without pagination.

API/Interface Requirements

interface InviteRole {
id: string;
label: string;
}
interface Invite {
id: string;
email: string;
role: InviteRole;
status: "pending" | "accepted" | "expired" | "revoked";
sentAt: string; // ISO datetime
acceptedAt?: string;
expiresAt?: string;
}
interface InviteFlowProps {
invites: Invite[];
roles: InviteRole[];
defaultRole?: string; // role id
loading?: boolean;
error?: string;
onRetry?: () => void;
onSendInvites: (payload: { emails: string[]; roleId: string }) => Promise<void>;
onResend?: (inviteId: string) => Promise<void>;
onRevoke?: (inviteId: string) => Promise<void>;
maxEmails?: number; // max emails per send, default 10
emptyTitle?: string;
emptyDescription?: string;
className?: string;
}

Accessibility Requirements

IDRequirement
A11Y-01Email input has aria-label and announces validation errors via aria-describedby.
A11Y-02Multi-email tags are keyboard navigable (arrow keys, Backspace to remove).
A11Y-03Role selector follows WAI-ARIA listbox/combobox pattern.
A11Y-04Status badges include text labels (not just color-coded).
A11Y-05Revoke confirmation dialog traps focus and returns focus to trigger on close.
A11Y-06All components pass axe-core automated checks with zero violations.

Content and Documentation Requirements

IDRequirement
DOC-01Storybook docs page with usage guidelines, prop table, and interactive examples.
DOC-02Recipe showing InviteFlow with TanStack Query for server-side invite management.
DOC-03Example showing InviteFlow + RoleAssignmentPanel (FRD-035) composed in a team settings page.

Dependencies

DependencyTypeRisk
src/components/ui/text-field.tsxInternalLow — email input.
src/components/ui/select.tsxInternalLow — role selector.
src/components/ui/badge.tsxInternalLow — status badges.
src/components/ui/button.tsxInternalLow — send/resend/revoke actions.
src/components/ui/alert-dialog.tsxInternalLow — revoke confirmation.
src/components/ui/status-card.tsxInternalLow — empty/error states.
src/components/ui/skeleton.tsxInternalLow — loading state.

Risks and Tradeoffs

RiskLikelihoodImpactMitigation
Multi-email tag input is complex to build accessiblyMediumMediumUse a proven tag-input pattern; test with screen readers. If too complex for v2.0, fall back to comma-separated plain text.
Email validation regex rejects valid edge-case emailsLowLowUse a permissive regex; server-side validation is authoritative.
Invite list grows long for large teamsLowMediumAdd pagination at 50+ invites; DataGrid supports this natively.

Open Questions

#QuestionOwnerStatus
OQ-01Should the form support a custom message field that accompanies the invite email?David HolmesOpen
OQ-02Should accepted invites appear in this list or only in the team member list (RoleAssignmentPanel)?David HolmesOpen
OQ-03Should the widget support a “Copy invite link” alternative for non-email invites?David HolmesOpen

Acceptance Criteria

#Criterion
AC-01Invite form validates email format and sends invites with selected role via onSendInvites.
AC-02Multi-email input allows adding multiple emails as removable tags.
AC-03Invite list displays all invites with correct status badges.
AC-04Resend action calls onResend for pending and expired invites.
AC-05Revoke action shows confirmation dialog and calls onRevoke.
AC-06Loading, empty, and error states render appropriate visual treatments.
AC-07All components pass axe-core checks with zero violations.
AC-08Storybook stories exist for all states and interaction flows.
AC-09pnpm typecheck and pnpm vitest run --project unit pass with zero errors.

LLM Handoff Instructions

When implementing this FRD:

  1. Create src/components/widgets/invite-flow.tsx.
  2. Invite form: Horizontal flex row with TextField (email), Select (role), Button (send). For multi-email, build a simple tag input: on comma or Enter, validate the email and add it as a tag. Tags are removable chips.
  3. Invite list: Use a styled list (or DataGrid for larger lists) showing email, role badge, status badge, sent date, and action buttons.
  4. Status badges: Use Badge with variant mapped from invite status — pending→warning, accepted→complete, expired→default (muted), revoked→destructive.
  5. Actions: Resend is a ghost Button. Revoke uses AlertDialog with destructive variant.
  6. State handling: Loading shows Skeleton; empty shows StatusCard with CTA pointing to the form; error shows StatusCard with retry.
  7. Stories in src/components/widgets/invite-flow.stories.tsx. Include: Default, Loading, Empty, Error, MultiEmail, WithPendingInvites, WithExpiredInvites.
  8. Tests in src/components/widgets/invite-flow.test.tsx. Test email validation, multi-email add/remove, send callback, resend/revoke flows.

Key files:

  • src/components/ui/text-field.tsx — email input.
  • src/components/ui/select.tsx — role selector.
  • src/components/ui/badge.tsx — status badges.
  • src/components/ui/alert-dialog.tsx — revoke confirmation.

Decision Log

DateDecisionRationale
2026-05-26Tag-input pattern for multi-email rather than a textarea.Better UX for adding/removing individual emails; visual clarity of who is being invited.
2026-05-26Accepted invites appear in the invite list with a read-only status.Provides a complete history; active member management is in RoleAssignmentPanel.
2026-05-26Client-side email validation is permissive; server validates authoritatively.Avoids rejecting valid but unusual email formats.

Document History

VersionDateAuthorChanges
0.12026-05-26David HolmesInitial draft.