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