# Account menu

Compose an account menu, settings dialog, invitations, and an undoable sign-out.

Import: @slices.sh/auth/headless

Public parts: 28

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/auth@^0.1.10
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/auth.md

## Anatomy

### Composition

```text
User.Root
  UserMenu.Root
    UserMenu.View name="main"
      UserMenu.Menu
        UserMenu.Trigger
        UserMenu.Content
      UserMenu.Manage
      UserMenu.ViewTrigger
      UserMenu.InvitationsButton
        UserMenu.InvitationBadge
      UserMenu.SignOut
      User.SignOut
      UserMenu.SignOutNotice
        UserMenu.UndoSignOut
      UserMenu.SignOutError
    UserMenu.SettingsDialog open
      UserMenu.DialogContent
        UserMenu.Settings
    UserMenu.InvitationsDialog
      UserMenu.InvitationsAvailable
        Invitations.Item
          Invitations.Accept
          Invitations.Decline
      UserMenu.InvitationsEmpty
      UserMenu.InvitationsLoading
      UserMenu.InvitationsError
      UserMenu.InvitationsRetry
      UserMenu.AcceptError
      UserMenu.DeclineError
    UserMenu.View name="organizations"
```

## Minimal composition

```tsx
"use client"

import "@/services"
import { UserMenu } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <UserMenu.Root>
      <UserMenu.Menu>
        <UserMenu.Trigger>My account</UserMenu.Trigger>
        <UserMenu.Content>
          <UserMenu.SignOut>Sign out</UserMenu.SignOut>
        </UserMenu.Content>
      </UserMenu.Menu>
    </UserMenu.Root>
  )
}

```

Requires an authenticated account. Organization controls also require organizations and the appropriate role.

## Incoming invitations

```tsx
"use client"

import "@/services"
import { User, Invitations } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <User.Root>
      <Invitations.Item>
        <Invitations.Accept>Accept</Invitations.Accept>
        <Invitations.Decline>Decline</Invitations.Decline>
      </Invitations.Item>
    </User.Root>
  )
}

```

Requires an authenticated account. Organization controls also require organizations and the appropriate role.

## Complete API reference

### User.SignOut

action · Element: button

SignOut is <button type="button"> with { pending } and onSignedOut. For a sign-out with a grace period and Undo, use UserMenu.SignOut.

Import: @slices.sh/auth/headless

```tsx
SignOutProps

type SignOutProps = PartProps<"button", { pending: boolean }> & { onSignedOut?: () => void }
```

Alternate import: User.SignOut from @slices.sh/auth/headless/user

Alternate import: User.SignOut from @slices.sh/auth/headless/passkeys

Alternate import: User.SignOut from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/sign-out.tsx

### UserMenu.Root

scope

Menu maps the open state onto a host menu through render={<DropdownMenu />}. Trigger keeps the reference focus returns to when a dialog closes. Content is the popup.

Import: @slices.sh/auth/headless

```tsx
UserMenuRootProps & AuthRootProps

export type UserMenuRootProps = RootProps & {
  shortcuts?: boolean
  onSignedOut?(): void
}
```

Alternate import: Root from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/root.tsx

### UserMenu.Menu

content

Menu maps the open state onto a host menu through render={<DropdownMenu />}. Trigger keeps the reference focus returns to when a dialog closes. Content is the popup.

Import: @slices.sh/auth/headless

UserMenu.Menu requires Root.

```tsx
PartProps<null>
```

Alternate import: Menu from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/menu.tsx

### UserMenu.Trigger

action · Element: button

Menu maps the open state onto a host menu through render={<DropdownMenu />}. Trigger keeps the reference focus returns to when a dialog closes. Content is the popup.

Import: @slices.sh/auth/headless

UserMenu.Trigger requires Root.

```tsx
PartProps<"button">
```

Alternate import: Trigger from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/trigger.tsx

### UserMenu.Content

content

Menu maps the open state onto a host menu through render={<DropdownMenu />}. Trigger keeps the reference focus returns to when a dialog closes. Content is the popup.

Import: @slices.sh/auth/headless

UserMenu.Content requires Root.

```tsx
PartProps<null>
```

Alternate import: Content from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/content.tsx

### UserMenu.Manage

action · Element: button

Manage section opens the settings dialog on that section (profile by default). SettingsDialog maps it onto render={<Dialog />}, DialogContent is its content, and Settings passes the selected section to a settings panel.

Import: @slices.sh/auth/headless

UserMenu.Manage requires Root.

```tsx
PartProps<"button"> & { section?: string }
```

Alternate import: Manage from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/manage.tsx

### UserMenu.SettingsDialog

content

Manage section opens the settings dialog on that section (profile by default). SettingsDialog maps it onto render={<Dialog />}, DialogContent is its content, and Settings passes the selected section to a settings panel.

Import: @slices.sh/auth/headless

UserMenu.SettingsDialog requires Root.

```tsx
PartProps<null>
```

Alternate import: SettingsDialog from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/settings-dialog.tsx

### UserMenu.DialogContent

content

Manage section opens the settings dialog on that section (profile by default). SettingsDialog maps it onto render={<Dialog />}, DialogContent is its content, and Settings passes the selected section to a settings panel.

Import: @slices.sh/auth/headless

UserMenu.DialogContent requires Root.

```tsx
PartProps<null>
```

Alternate import: DialogContent from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/dialog-content.tsx

### UserMenu.Settings

content

Manage section opens the settings dialog on that section (profile by default). SettingsDialog maps it onto render={<Dialog />}, DialogContent is its content, and Settings passes the selected section to a settings panel.

Import: @slices.sh/auth/headless

UserMenu.Settings requires Root.

```tsx
PartProps<null>
```

Alternate import: Settings from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/settings.tsx

### UserMenu.View

content · Element: div

View name="main" | "organizations" replaces the menu's items in place; ViewTrigger view switches to one and moves focus once its items are registered.

Import: @slices.sh/auth/headless

UserMenu.View requires Root.

```tsx
PartProps<"div"> & { name: "main" | "organizations" }
```

Alternate import: View from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/view.tsx

### UserMenu.ViewTrigger

action · Element: button

View name="main" | "organizations" replaces the menu's items in place; ViewTrigger view switches to one and moves focus once its items are registered.

Import: @slices.sh/auth/headless

UserMenu.ViewTrigger requires Root.

```tsx
PartProps<"button"> & { view: "main" | "organizations" }
```

Alternate import: ViewTrigger from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/view-trigger.tsx

### UserMenu.SignOut

action · Element: button

SignOut starts a five-second grace period, with { pending, retryAfter }. SignOutNotice is a status region with { pending, seconds }; UndoSignOut cancels before any request. SignOutError is <p role="alert"> with { message }.

Import: @slices.sh/auth/headless

UserMenu.SignOut requires Root.

```tsx
PartProps<"button", { pending: boolean; retryAfter: number }>
```

Alternate import: SignOut from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/sign-out.tsx

### UserMenu.SignOutNotice

content · Element: div

SignOut starts a five-second grace period, with { pending, retryAfter }. SignOutNotice is a status region with { pending, seconds }; UndoSignOut cancels before any request. SignOutError is <p role="alert"> with { message }.

Import: @slices.sh/auth/headless

UserMenu.SignOutNotice requires Root.

```tsx
PartProps<"div", { pending: boolean; seconds: number }>
```

Alternate import: SignOutNotice from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/sign-out-notice.tsx

### UserMenu.UndoSignOut

action · Element: button

SignOut starts a five-second grace period, with { pending, retryAfter }. SignOutNotice is a status region with { pending, seconds }; UndoSignOut cancels before any request. SignOutError is <p role="alert"> with { message }.

Import: @slices.sh/auth/headless

UserMenu.UndoSignOut requires Root.

```tsx
PartProps<"button">
```

Alternate import: UndoSignOut from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/undo-sign-out.tsx

### UserMenu.SignOutError

content · Element: p

SignOut starts a five-second grace period, with { pending, retryAfter }. SignOutNotice is a status region with { pending, seconds }; UndoSignOut cancels before any request. SignOutError is <p role="alert"> with { message }.

Import: @slices.sh/auth/headless

UserMenu.SignOutError requires Root.

```tsx
PartProps<"p", { message: string }>
```

Alternate import: SignOutError from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/sign-out-error.tsx

### UserMenu.InvitationsAvailable

gate

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps
```

Alternate import: InvitationsAvailable from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-available.tsx

### UserMenu.InvitationBadge

content · Element: span

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"span", { count: number }>
```

Alternate import: InvitationBadge from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitation-badge.tsx

### UserMenu.InvitationsButton

action · Element: button

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

UserMenu.InvitationsButton requires Root.

```tsx
PartProps<"button", { count: number | null }>
```

Alternate import: InvitationsButton from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-button.tsx

### UserMenu.InvitationsDialog

content

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

UserMenu.InvitationsDialog requires Root.

```tsx
PartProps<null>
```

Alternate import: InvitationsDialog from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-dialog.tsx

### UserMenu.InvitationsEmpty

gate

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps
```

Alternate import: InvitationsEmpty from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-empty.tsx

### UserMenu.InvitationsLoading

gate

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps
```

Alternate import: InvitationsLoading from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-loading.tsx

### UserMenu.InvitationsError

content · Element: p

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"p", { message: string }>
```

Alternate import: InvitationsError from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-error.tsx

### UserMenu.InvitationsRetry

action · Element: button

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"button">
```

Alternate import: InvitationsRetry from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/invitations-retry.tsx

### UserMenu.AcceptError

content · Element: p

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"p", { message: string }>
```

Alternate import: AcceptError from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/accept-error.tsx

### UserMenu.DeclineError

content · Element: p

InvitationsAvailable opens for a verified account where organizations exist. InvitationBadge and InvitationsButton get { count }. InvitationsDialog maps onto a host dialog; inside it InvitationsLoading, InvitationsEmpty, InvitationsError and InvitationsRetry cover the read, Invitations.Item lists them, and AcceptError and DeclineError report the writes.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"p", { message: string }>
```

Alternate import: DeclineError from @slices.sh/auth/headless/user-menu

Source: @slices.sh/auth/src/headless/user-menu/decline-error.tsx

### Invitations.Item

content · Element: div

Item repeats once per invitation with { organizationName }. Accept and Decline act on it with { pending }; their refusals show in UserMenu.AcceptError and DeclineError.

Import: @slices.sh/auth/headless

Capability: organizations

Invitations.Item renders its own scope.

```tsx
ItemProps

type ItemProps = PartProps<"div", { organizationName: string }>
```

Alternate import: Invitations.Item from @slices.sh/auth/headless/invitations

Source: @slices.sh/auth/src/headless/invitations/item.tsx

### Invitations.Accept

action · Element: button

Item repeats once per invitation with { organizationName }. Accept and Decline act on it with { pending }; their refusals show in UserMenu.AcceptError and DeclineError.

Import: @slices.sh/auth/headless

Capability: organizations

Invitations.Accept must be inside <Invitations.Item>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: Invitations.Accept from @slices.sh/auth/headless/invitations

Source: @slices.sh/auth/src/headless/invitations/accept.tsx

### Invitations.Decline

action · Element: button

Item repeats once per invitation with { organizationName }. Accept and Decline act on it with { pending }; their refusals show in UserMenu.AcceptError and DeclineError.

Import: @slices.sh/auth/headless

Capability: organizations

Invitations.Decline must be inside <Invitations.Item>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: Invitations.Decline from @slices.sh/auth/headless/invitations

Source: @slices.sh/auth/src/headless/invitations/decline.tsx

### User.Root

scope

Says which auth the User parts below read, and whether they may do without it. The parts need
no root: left out, they read the nearest auth root's config, or "default", and render nothing
without auth. `<User.Root enabled="optional">` is for the one who wants that said in the code.

Import: @slices.sh/auth/headless

```tsx
RootProps & AuthRootProps
```

Alternate import: User.Root from @slices.sh/auth/headless/user

Alternate import: User.Root from @slices.sh/auth/headless/passkeys

Alternate import: User.Root from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/root.tsx
