Share sheetPro
A Share button that opens into a panel with copy link, access level, channels, and people to send to.
Live · keyboard ready
- A Share button on a document, design, or board header where people share often.
- Sharing flows that combine copy link, access level, and sending to a few recent people.
- Posting to a small set of channels, such as Slack or email, with in-place pending and done feedback.
- Use copy-button when all you need is to copy a URL.
- Use dialog when sharing needs a full form, such as roles per person or an email search.
- Use popover for a small menu of share targets with no people picker.
Installation
Pro source and install commands unlock with a Pro plan.
example.tsx
import { ShareSheet } from "@/registry/components/share-sheet/share-sheet";import { Globe, Lock, Mail, MessageSquare } from "lucide-react"; export function DocumentShare() { return ( <ShareSheet title="Q3 roadmap" link="https://example.com/d/q3-roadmap" people={[ { id: "ana", name: "Ana Ruiz", avatar: "/avatars/ana.jpg" }, { id: "sam", name: "Sam Lee", avatar: "/avatars/sam.jpg" }, ]} access={[ { value: "team", label: "Team", description: "Anyone on the team can view", icon: <Lock size={16} /> }, { value: "public", label: "Anyone with the link", description: "Can view and comment", icon: <Globe size={16} /> }, ]} channels={[ { id: "slack", label: "Slack", doneLabel: "Posted", icon: <MessageSquare size={16} /> }, { id: "email", label: "Email", doneLabel: "Sent", icon: <Mail size={16} /> }, ]} onSend={({ people, access }) => api.share(people.map(person => person.id), access)} onChannel={channel => api.post(channel)} /> );}ShareSheet
A Share button that springs open into a share panel (copy link, access level, channels, recent people) and folds into a sent confirmation.
PropTypeDefaultDescription
titleRequiredstring–Name of the thing being shared, shown in the panel title as Share "title".linkRequiredstring–The URL shown in the link row and written to the clipboard by Copy.peopleRequiredSharePerson[]–Recent people: { id, name, avatar }. Picking one flies its avatar up into the Send to row.accessRequiredShareAccess[]–Access levels: { value, label, description, icon }. Shown in a listbox under the link.channelsShareChannel[][]Channel buttons: { id, label, icon, doneLabel }. doneLabel is shown once onChannel resolves; keep it about as long as label.defaultAccessstring–Starting access value. Defaults to the first entry in access.onSend(submission: ShareSubmission) => void | Promise<unknown>–Receives { people, access }. A returned promise holds the sending state; a rejection keeps the panel open with an error.onChannel(channel: string) => void | Promise<unknown>–Called with the channel id. A promise holds that button's pending state; a rejection shows Failed on it.onCopy(access: string) => void–Called after the link is copied, with the access level it was copied under.labelstring"Share"Trigger button text.align"start" | "end""end"Which edge of the button stays put while the panel grows.classNamestring–Class on the root.- EnterorSpace
- Opens the panel from the Share button, or presses the focused control.
- Escape
- Closes the access list first, then folds the panel back into the button and returns focus to it.
- ArrowUporArrowDownorArrowLeftorArrowRight
- Moves between access options in the open list, looping at the ends.
- Tab
- Moves through the copy, access, channel, people, and send controls.
- The trigger has aria-haspopup="dialog"; the open panel is role="dialog" labelled by its title.
- Opening moves focus to Copy, sending moves it to Done, and closing returns it to the Share button.
- Access options are a listbox of role="option" buttons with aria-selected and roving tabindex.
- People are toggle buttons with aria-pressed; chips have a Remove <name> button.
- A polite status region announces copies, picks, removals, channel results, and who a share was sent to.
- A press outside the component closes the panel without stealing focus.
- One shape springs its width, height, and corner radius between the button, the panel, and the confirmation; faces cross with a short blur and scale.
- A picked avatar flies from the recent row to its chip on a separate layer, so the scrolling chips row never clips it.
- Send with nobody picked shakes the Send to row on a spring; the count in the send button rolls one digit up or down.
- Reduced motion jumps the shape to size, swaps faces with a plain fade, skips the avatar flight and the shake, and slows the spinner.
- The panel is min(24rem, 100vw - 2rem) wide; set --share-sheet-width on an ancestor to fit a narrower container. It is a container, so its rows adapt to that width.
- The panel is min(24rem, 100vw - 2rem) wide and the confirmation min(19rem, 100vw - 2rem), so both fit a 320px phone.
- align="start" grows the panel from the left edge of the button; use it when the button sits near the left of the viewport.
- Hover styles apply only on hover-capable fine pointers; the trigger uses touch-action: manipulation for fast taps.
- Each face runs a ResizeObserver while it is current, and the shape animates three motion values; nothing runs while closed.
- Avatars are plain img tags from your URLs; pass small, pre-sized images since each person renders at up to 80px.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use for sharing a document, board, or file from a toolbar without leaving the page.
- Wire onSend and onChannel to your API and return the promise; the component handles pending, error, and done states itself.
- people is a short list of recent collaborators, not a directory search. For inviting by email use the invite-people block.
- The root reserves only the button's size; the panel floats over the page, so leave room below or pick align to grow away from an edge.
The full library index for assistants is at /llms.txt.