# User Menu

> A React user menu: profile header, theme switch, account links, other accounts and sign out, as a dropdown or a bottom sheet on phones.

- Type: React component (Navigation)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add user-menu`
- Docs: https://wingo-ui.com/components/user-menu

## Interaction

Click the avatar (or the card in a sidebar footer, or the compact avatar and first name) and the menu grows out of it: a profile header with the plan badge and a Manage account button, a Light / Dark / System row whose raised thumb slides to the pick, your links, the other signed-in accounts and Sign out. Picking a theme keeps the menu open. Picking another account spins its row until the switch finishes, then the menu closes; Sign out spins and stays open until your promise settles (a rejection only stops the spinner). Keyboard: Enter, Space or Down on the trigger opens it on the first row, Up / Down move through every row including the three theme options, Right opens a submenu (Language), Left closes it, typing a letter jumps to a row, item shortcuts (Mod+,) run while it is open, Escape closes and focus returns to the trigger. A shortcut prop opens it from anywhere. In a collapsed sidebar the card shrinks to the avatar. On a phone the menu is a bottom sheet: a centered profile card, the theme as a full-width segmented control, 48px rows, submenus that push in from the right with a Back row, and a handle to drag it away.

## Installation

```bash
npx wingo-ui@latest add user-menu
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/user-menu` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/user-menu.tsx`

npm dependencies: `npm i lucide-react motion`

Wingo UI dependencies (installed automatically):

- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [useShortcut](https://wingo-ui.com/components/use-shortcut) ([Markdown](https://wingo-ui.com/components/use-shortcut.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
"use client"

import { CreditCard, Settings, User } from "lucide-react"
import { useTheme } from "next-themes"
import { UserMenu, type UserMenuTheme } from "@/components/ui/user-menu"

export function AccountMenu({ onSignOut }: { onSignOut: () => Promise<void> }) {
  const { theme, setTheme } = useTheme()
  return (
    <UserMenu
      user={{ name: "Emma Carter", email: "emma.carter@northwind.com", avatar: "/avatars/emma.jpg", plan: "Pro", status: "online" }}
      theme={theme as UserMenuTheme}
      onThemeChange={setTheme}
      themeLabels={{ title: "Theme" }}
      manageHref="/settings/profile"
      manageLabel="Manage account"
      items={[
        { label: "Profile", icon: <User />, href: "/settings/profile" },
        { label: "Settings", icon: <Settings />, shortcut: "mod+,", href: "/settings" },
        { label: "Billing", icon: <CreditCard />, badge: "Pro", href: "/settings/billing" },
      ]}
      signOutLabel="Sign out"
      onSignOut={onSignOut}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `user?` | `UserMenuUser` | `"{ name: \"Emma Carter\", email: \"emma.carter@northwind.com\" }"` | The signed-in person: { id?, name, email?, avatar?, status?: "online" \| "offline" \| "busy" \| "away", plan?: ReactNode, role?: ReactNode }. Without a photo the avatar shows initials on a color from the name; long names truncate and keep the full name in a tooltip. |
| `items?` | `MenuEntry[]` | `"[]"` | Rows between the header and the accounts, in dropdown-menu's entry format: items ({ label, icon, description, shortcut, badge, href, onSelect, tone, disabled, keepOpen }), submenus, checkboxes, radio sets, groups, labels and separators. href items render through NavLink (the app's router link). |
| `trigger?` | `"avatar" \| "card" \| "compact" \| ReactNode` | `"avatar"` | avatar: an avatar button for top bars. card: avatar, name, email and an up-down chevron filling the width, for sidebar footers; inside a collapsed sidebar (data-collapsed="true" on a group/sidebar parent) only the avatar stays. compact: avatar, first name and a chevron. Any element works too (it gets the trigger props). For custom compositions use UserMenuTrigger (see Parts). |
| `size?` | `"sm" \| "md"` | `"md"` | The trigger avatar: 32 / 40px (card 28 / 32px, compact 24 / 32px). Every size has a 44px hit area on touch. |
| `theme?` | `"light" \| "dark" \| "system"` | `"system"` | The current theme. Pass next-themes' theme here; left out, the row starts on system and keeps its own state. |
| `onThemeChange?` | `(theme: UserMenuTheme) => void` |  | Shows the theme row (Light / Dark / System) and fires on every pick. Wire next-themes: const { theme, setTheme } = useTheme(). |
| `themeLabels?` | `{ title?: string; light?: string; dark?: string; system?: string }` |  | The theme row's heading and option labels; each one falls back to English: { title: "Theme", light: "Light", dark: "Dark", system: "System" }. |
| `accounts?` | `UserMenuUser[]` |  | Other signed-in accounts. They are listed under the current one (which gets a check), each switchable. |
| `onAccountChange?` | `(account: UserMenuUser) => (void \| Promise<unknown>)` |  | Picking another account. Return a promise and its row spins while the menu stays open, then the menu closes. |
| `onAddAccount?` | `() => void` |  | Shows an Add account row under the accounts. |
| `addAccountLabel?` | `string` | `"Add account"` | The Add account row's label. |
| `accountsLabel?` | `string` | `"Accounts"` | The heading above the accounts. |
| `manageHref?` | `string` | `""` | Shows a Manage account button under the profile header that links here (through the router link). |
| `onManage?` | `() => void` |  | Shows the Manage account button and runs on press (with or without manageHref). |
| `manageLabel?` | `string` | `"Manage account"` | The Manage account button's label. |
| `onSignOut?` | `() => (void \| Promise<unknown>)` |  | Shows the red Sign out row. Return a promise to keep the menu open with a spinner on the row until it settles; a rejection stops the spinner and leaves the menu open. |
| `signOutLabel?` | `string` | `"Sign out"` | The sign-out row's label. |
| `side?` | `"auto" \| "top" \| "right" \| "bottom" \| "left"` | `"auto"` | Where the menu opens. auto: below the trigger, or above it for the card trigger (sidebar footers). It flips when there is no room; on phones (below 768px, with responsive off) left and right fall back to auto. |
| `align?` | `"auto" \| "start" \| "center" \| "end"` | `"auto"` | Alignment against the trigger. auto: end (the avatar sits at the end of a top bar), start for the card trigger. |
| `open?` | `boolean` |  | Controlled open state; pair it with onOpenChange. |
| `defaultOpen?` | `boolean` | `false` | Starts open (uncontrolled). |
| `onOpenChange?` | `(open: boolean) => void` |  | Fires when the menu or the sheet opens or closes. |
| `responsive?` | `boolean` | `true` | Below 768px the menu becomes the account sheet. Off: the dropdown on every screen. |
| `sheetTitle?` | `string` | `"Account"` | The phone sheet's title, read by screen readers (it is not shown). |
| `backLabel?` | `string` | `"Back"` | The back row of a submenu page in the phone sheet. |
| `shortcut?` | `string` | `""` | A key combo in mod+k notation that opens and closes the menu from anywhere (e.g. shift+mod+u); the trigger gets aria-keyshortcuts. Empty binds nothing. |
| `triggerLabel?` | `(name: string) => string` | `"(name) => \"Account menu, \" + name"` | The trigger's accessible name. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for href items and the manage link. Default: the one set with LinkProvider, else a plain <a>. |
| `container?` | `HTMLElement \| null` |  | Portal target for the menu and the sheet (default document.body). |
| `classNames?` | `Partial<Record<"trigger" \| "avatar" \| "name" \| "email" \| "chevron" \| "content" \| "header" \| "headerAvatar" \| "plan" \| "manage" \| "theme" \| "item" \| "accounts" \| "addAccount" \| "signOut", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the built-in trigger, merged last. |

## Parts

### UserMenuTrigger (component)

The built-in trigger alone, for custom compositions: another menu, a popover or a link to the profile page. Takes every button prop plus the ones below.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `user?` | `UserMenuUser` | `"{ name: \"Emma Carter\", email: \"emma.carter@northwind.com\" }"` | The person shown: name, email, avatar, status, plan and role. |
| `variant?` | `"avatar" \| "card" \| "compact"` | `"avatar"` | An avatar button, a full-width card with name and email for sidebar footers, or avatar plus first name. |
| `size?` | `"sm" \| "md"` | `"md"` | The avatar at 32 / 40px (card 28 / 32px, compact 24 / 32px). |
| `triggerLabel?` | `(name: string) => string` | `"(name) => \"Account menu, \" + name"` | The accessible name. |
| `classNames?` | `Partial<Record<"avatar" \| "name" \| "email" \| "chevron", string>>` |  | Extra classes for the inner parts. |

### UserMenuUser (type)

The signed-in person or another account: { id?, name, email?, avatar?, status?, plan?, role? }. id defaults to the email, then the name.

### UserMenuEntry (type)

One entry of items, the same type as the dropdown menu: an item (a link or an action, with icon, shortcut and badge), a checkbox, a radio group, a submenu, a group, a label or a separator.

## Examples

- **Sidebar footer**: The card trigger in a collapsed rail (avatar only, the menu opens to the right) and in an expanded sidebar.
- **Compact trigger in a top bar**: Avatar and first name next to search and notifications; the theme row switches this page.
- **Several accounts**: Switch between signed-in accounts with a 700ms spinner on the row, plus Add account.
- **Custom items, no theme row**: Groups, a checkbox and an action; without onThemeChange the theme row stays out.
- **Phone: the account sheet**: What the menu becomes below 768px: a profile card, a segmented theme control and sheet rows.
- **Small, localized, with a shortcut**: size sm in a dense bar, custom labels and aria-label, opened with Shift+Mod+U.

## See also

- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Sidebar](https://wingo-ui.com/components/sidebar) ([Markdown](https://wingo-ui.com/components/sidebar.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))

---

Canonical: https://wingo-ui.com/components/user-menu
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
