KQ-1330 · INVITE/FLOW

Invite Flow

An invite form whose addresses become chips as you type a separator, or several at once from a pasted list: each pops in on snap a cascade step after the last, and one that is not an address, is already a member or is past the seats left shakes on a five-key tween once it lands and stays tinted with the reason in its name. Every invitee gets a role (in its chip, in a list with a segmented control sliding on snap, or once for everyone) and the note is optional; Send folds each chip into an envelope where it sits on flick, closes the flap and flies it out across the right edge, x on the exit ease and y on the enter ease so the path rises first, then the count rolls up on snap in a summary of who was invited as what. The chips are a list with roving focus (arrows move, Delete removes, Enter fixes or opens a role listbox) and Ctrl or Cmd with Enter sends from anywhere.

KQ-1330 · INVITE/FLOW

Invite to Fernworks

2 of 4 seats

2 seats left · Separate with commas or spaces.

From Dana Kim400

2 to invite · 2 seats left

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/invite-flow

Props

value / defaultValue
Invitee[]
default — / defaultInvitees
Every address entered, valid or not, with its role.
onValueChange
(invitees: Invitee[]) => void
default —
Fires from the keystroke, paste, role pick or removal that changed the list, and with [] after a send.
message / defaultMessage
string
default — / ""
The note sent with the invites.
onMessageChange
(message: string) => void
default —
Fires as the note is typed.
roleOptions
InviteRole[]
default defaultInviteRoles
The roles an invitee can have: id, label and a one-line description.
defaultRole
string
default "member"
The role a new address starts with.
members
string[]
default —
Addresses already in the workspace; entered again, they are flagged.
seats
number
default —
Seats left; addresses past it are flagged. Omit for no limit.
onSend
(invites: Invitee[], message: string) => void | Promise<void>
default —
Sends the invites. A promise holds Send pending; a rejection's message is shown and nothing leaves.
onDone
() => void
default —
Invite more was pressed on the summary.
inviteLink
string
default "fieldline.app/join/fernworks"
The shareable join link offered on the summary.
onCopyLink
(link: string) => void
default —
The link was copied.
chips
"avatar" | "email" | "name"
default "avatar"
How a chip shows its person: a tinted initial and the address, the address alone, or a name read from it.
roles
"chip" | "list" | "single"
default "chip"
Where roles are picked: in each chip, in a list under the field, or once for everyone.
send
"envelopes" | "batch" | "fade"
default "envelopes"
How Send looks: chips fold into envelopes and fly out, gather on Send into one envelope, or tick and fade.
workspace
string
default "Fernworks"
The workspace's name, in the heading and the messages.
sender
string
default "Dana Kim"
Who the note is from, shown under it.
placeholder
string
default "Add emails, or paste a list"
The empty field's hint.
maxLength
number
default 400
The longest note, in characters.
status
"ready" | "loading" | "error"
default "ready"
Loading draws placeholder chips; error offers Retry.
onRetry
() => void
default —
The Retry button of the error state.
label
string
default —
The region's accessible name when it should differ from the heading.
sound
boolean
default false
A pop for each chip you add (rising a semitone each) and each role change; a swish for each envelope leaving.
disabled
boolean
default false
Shows the form but takes no input.
className
string
default —
Classes for the region; give it a max height and it scrolls inside itself.