Share Dialog React block for Tailwind CSS v4
v1.0.0Added in 1.10.0
The Share dialog of a collaborative app: invite people by email with a role, manage who has access, set link access and copy the link.
Share opens the dialog with the cursor in the email field (a bottom sheet below 768px). Type or paste addresses: Enter, a comma or a space turns each into a chip, Backspace brings the last one back as text, and people from suggestions show their name and photo. A bad address or someone who already has access stays as a red chip with the reason under the field. Pick the role, add an optional message (mod+Enter sends) and press Invite: the button loads, then the new people slide into the list as Pending with a Resend link and a toast confirms. A failed invite keeps the chips and shows the error inline. In the list change anyone's role from their picker, or open the ... menu to resend, make them the owner or remove access after a confirmation; the owner's row is fixed. Under General access switch between Restricted, Anyone at the domain and Anyone with the link, and pick the link role. Copy link swaps its label to Link copied and shows a toast; Done or Escape closes. Read-only viewers see the list and copy the link but change nothing.
Share Dialog is a Pro React block in the React overlay components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add share-dialog, together with Alert Dialog, Avatar, Badge, Button, Copy Button and 9 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add share-dialogPro item: the CLI needs a Pro account. See plans
Usage
Import ShareDialog from @/components/blocks/share-dialog.
"use client"
import { ShareDialog } from "@/components/blocks/share-dialog"
import type { ShareDialogPerson } from "@/components/blocks/share-dialog-data"
export function ShareDoc({ docId, people }: { docId: string; people: ShareDialogPerson[] }) {
return (
<ShareDialog
resourceName="Q4 Launch Plan"
link={`https://docs.acme.com/d/${docId}`}
defaultPeople={people}
onInvite={async (emails, role, message) => {
const res = await fetch(`/api/docs/${docId}/invites`, { method: "POST", body: JSON.stringify({ emails, role, message }) })
if (!res.ok) throw new Error("The invites were not sent. Try again.")
}}
onRoleChange={(personId, role) =>
fetch(`/api/docs/${docId}/members/${personId}`, { method: "PATCH", body: JSON.stringify({ role }) })
}
onRemove={(personId) => fetch(`/api/docs/${docId}/members/${personId}`, { method: "DELETE" })}
onAccessChange={(access) => fetch(`/api/docs/${docId}`, { method: "PATCH", body: JSON.stringify({ access }) })}
/>
)
}Props
resourceNamedefault"Q4 Launch Plan"stringThe file, doc or project being shared; it fills the title (Share "Q4 Launch Plan") and the remove confirmation.
titleReactNode | ((name: string) => ReactNode)The dialog title, or a function of resourceName. Default Share "name" from labels.title.
descriptiondefault""ReactNodeA line under the title, e.g. who can see the file today.
peopleShareDialogPerson[]Controlled list of people with access: { id, name, email, avatar?, role, owner?, pending? }. Pair it with onPeopleChange, which fires after invites, role changes, removals and a new owner.
defaultPeopledefault"SHARE_DIALOG_PEOPLE"ShareDialogPerson[]Starting list while uncontrolled; the sample is an owner, an editor, a commenter and a pending invite.
onPeopleChange(people: ShareDialogPerson[]) => voidThe list after every change the dialog makes.
rolesdefault"DEFAULT_SHARE_DIALOG_ROLES"ShareDialogRole[]The role options { value, label, description? } for invites, rows and the link. Default Viewer, Commenter and Editor.
suggestionsdefault"[]"ShareDialogPerson[]People the email field suggests while typing (name, address and photo); invited ones keep their name and photo in the list.
linkdefault"https://docs.acme.com/d/q4-launch-plan"stringThe address Copy link puts on the clipboard.
domaindefault"acme.com"stringThe organization domain for the "Anyone at acme.com" access option. Empty hides that option.
labelsPartial<ShareDialogLabels>Merged over DEFAULT_SHARE_DIALOG_LABELS. Every string: the trigger, title, placeholder, roles, toasts, confirmations, access options and buttons. Functions take the name or count.
triggerReactNodeThe element that opens the dialog, rendered with asChild. Default a soft Share button; null renders none, for a dialog driven by open.
defaultInviteRoledefault"viewer"stringThe role preselected for new invites.
defaultAccessdefault"restricted""restricted" | "domain" | "anyone"Starting general access while uncontrolled: only people with access, anyone at the domain, or anyone with the link.
access"restricted" | "domain" | "anyone"Controlled general access; pair it with onAccessChange.
onAccessChange(access: ShareDialogAccess) => voidA new general access was picked.
defaultLinkRoledefault"viewer"stringStarting role for people who open the link, shown while access is not restricted.
linkRolestringControlled link role; pair it with onLinkRoleChange.
onLinkRoleChange(role: string) => voidA new link role was picked.
onInvite(emails: string[], role: string, message?: string) => void | Promise<unknown>Invite was pressed with valid, new addresses. Return a promise to show the loading state; a rejection keeps the chips and shows its message under the field.
onRoleChange(personId: string, role: string) => void | Promise<unknown>A row's role changed. It updates at once; a rejection puts the old role back with an error toast.
onRemove(personId: string) => void | Promise<unknown>Remove access was confirmed. A rejection shows in the confirmation and keeps the row.
onTransferOwnership(personId: string) => void | Promise<unknown>Adds Make owner to each row's menu; after the confirmation that person becomes the owner and the old owner an editor.
onResend(personId: string) => void | Promise<unknown>Resend on a pending invite.
showMessagedefaulttruebooleanA message field slides in under the invite row once there are addresses.
readOnlydefaultfalsebooleanFor viewers: no invite row, roles and access as plain text, no row menus. Copy link still works.
defaultOpendefaultfalsebooleanOpens on mount while uncontrolled.
openbooleanControlled open state; pair it with onOpenChange. The dialog stays open while an invite is sending.
onOpenChange(open: boolean) => voidDone, the close button, Escape, an outside click or the sheet drag.
sizedefault"md""md" | "lg"Dialog width: md 520px, lg 720px. Below 768px it is a bottom sheet either way.
responsivedefaulttruebooleanA bottom sheet below 768px, with the role pickers and row menus as sheets too.
classNamestringClasses for the dialog content. Other props go to the dialog content too (onOpenAutoFocus, aria-*).
classNamesPartial<Record<ShareDialogSlot, string>>Classes for overlay, content, header, body, footer, invite, people, person, access, copy and done.
Parts
DEFAULT_SHARE_DIALOG_LABELS
constantThe English strings; spread it and override what you need for another language.
DEFAULT_SHARE_DIALOG_ROLES
constantViewer, Commenter and Editor with a short description each.
SHARE_DIALOG_PEOPLE
constantSample people with access (share-dialog-data.ts): an owner, an editor, a commenter and a pending invite.
SHARE_DIALOG_SUGGESTIONS
constantSample teammates for the suggestions prop.
Examples
Shared with four people
An owner, an editor, a commenter and a pending invite; suggestions, Make owner and Remove access with a confirmation.
Anyone with the link
defaultAccess="anyone" with commenters by link, opened from a custom trigger.
Invite error
The first invite fails: the chips stay and the message shows under the field. Try again and it goes through.
Read-only viewer
readOnly: the list and the access as text, Copy link still works.
Large, no message
size="lg" with a description, editors by default and showMessage={false}.
Dependencies
Files
- components/blocks/share-dialog.tsx
- components/blocks/share-dialog-data.ts
FAQ
Is the React Share Dialog block free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Share Dialog?
Run npx wingo-ui@latest add share-dialog. It also adds the Wingo UI items it builds on (Alert Dialog, Avatar, Badge, Button, Copy Button and 9 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/share-dialog.
How does Share Dialog behave on phones?
A bottom sheet below 768px, with the role pickers and row menus as sheets too. The responsive prop controls this and is on by default.