Xzero UI
Pro library

Share accessPro

A share panel: invite people as validated email chips with suggestions, change roles from a compact menu, remove with undo, switch link access and copy the link, with every change landing in place.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/share-access
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/share-access

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import * as Popover from "@radix-ui/react-popover";
import { ShareAccess } from "@/components/xzero/share-access/share-access";

export function ShareButton({ doc }: { doc: Doc }) {
  const [open, setOpen] = useState(false);
  const { data: members, mutate } = useMembers(doc.id);

  return (
    <Popover.Root open={open} onOpenChange={setOpen}>
      <Popover.Trigger>Share</Popover.Trigger>
      <Popover.Portal>
        <Popover.Content align="end" sideOffset={8}>
          <ShareAccess
            title={doc.title}
            members={members}
            onMembersChange={next => mutate(api.setMembers(doc.id, next), { optimisticData: next })}
            directory={workspace.people}
            link={doc.url}
            selfId={me.id}
            onInvite={(people, role) => api.invite(doc.id, people, role)}
            onDone={() => setOpen(false)}
          />
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  );
}

API reference

ShareAccess

A share panel for a document, board or project. Type names or emails and they become chips: people from your directory show their photo, and an address that is not valid turns red with a message under the field. Suggestions with avatars appear as you type. Pick the role for the invite and send; the new people slide into the list with a brief highlight. Each person's role changes from a compact menu whose label morphs to its new width, Remove access folds the row away with an undo, general access switches between restricted and anyone with the link, and Copy link confirms in place without changing width.

PropTypeDefaultDescription
titlestring"Q3 launch plan"Name of what is being shared, used in the heading Share “title” and the accessible name.
membersreadonly ShareAccessMember[]–People with access: { id, email, name?, src?, role: "owner" | "editor" | "commenter" | "viewer", pending? }. Pass with onMembersChange to control the list.
defaultMembersreadonly ShareAccessMember[][]Initial members when uncontrolled.
onMembersChange(members: ShareAccessMember[]) => void–Called with the new list after an invite, a role change, a removal or an undo.
access"restricted" | "link"–Who can open it without an invite. Pass with onAccessChange to control it.
defaultAccess"restricted" | "link""restricted"Initial general access when uncontrolled.
onAccessChange(access: ShareGeneralAccess) => void–Called when general access changes.
linkRole"viewer" | "commenter" | "editor"–What anyone with the link can do. Its menu slides in beside general access when the link is open.
defaultLinkRoleShareRole"viewer"Initial link role when uncontrolled.
onLinkRoleChange(role: ShareRole) => void–Called when the link role changes.
directoryreadonly ShareAccessContact[][]People to suggest while typing: { email, name?, src? }. Matches the start of any name part or the email; people who already have access or are already chips are left out. Typing a directory name exactly also resolves to that person.
linkstring–The link Copy link puts on the clipboard.
selfIdstring–Id of the member using the app, marked (you).
defaultInviteRoleShareRole"editor"Role preselected for new invites.
onInvite(people: ShareAccessContact[], role: ShareRole) => void | Promise<void>–Called with the valid chips and role when Invite is pressed. Return a promise to show Sending; if it rejects, the chips stay and a message explains it. Once it resolves the people join the list as pending.
onCopyLink(link: string) => void–Called after the link is on the clipboard.
onDone() => void–Shows a Done button that calls this, for example to close the popover.
classNamestring–Class on the panel.
styleCSSProperties–Inline styles on the panel.

Keyboard interactions

KeysAction
Type, then EnterAdds the highlighted suggestion, or turns what you typed into a chip. With chips and nothing typed, Enter sends the invite.
Comma / Semicolon / TabTurns what you typed into a chip. Space does too once the text contains an @.
Arrow Down / Arrow UpMoves through suggestions.
BackspaceIn an empty field, the first press marks the last chip and the second removes it.
EscapeClears the text and closes suggestions; in a menu, closes the menu and returns focus to its trigger.
Enter / Space on a roleOpens the role menu; arrows move, Enter picks, and typing a letter jumps to an option.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in overlays

Guidance for AI tools

Share access: A share panel: invite people as validated email chips with suggestions, change roles from a compact menu, remove with undo, switch link access and copy the link, with every change landing in place. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components