Skip to content

Components

UserMenu

The signed-in user's corner of the app: an avatar that opens a dropdown with their identity and account actions. Standalone, customizable, replaceable — and theme-aware inside a Header.

In the header

Drop it into the Header’s actions slot. The trigger shows the avatar; the panel opens with an identity header (name + detail line) above your items. It closes on outside click, Escape, or any item click — and inside a primary/dark Header the trigger’s hover ring adapts automatically.

Open fullscreen
9:41
header.tsx
import { AppShell, Header, UserMenu, UserMenuItem } from "appshell-react";
import { LogOut, Settings, User } from "lucide-react";

<Header
  behavior="sticky"
  logo={<span className="font-bold">Console</span>}
  actions={
    <UserMenu
      username="Mara Kealoha"
      detail="mara@terra.dev"
      initials="MK"                        // or src="/avatar.jpg"
    >
      <UserMenuItem icon={<User />} label="Profile" href="/profile" />
      <UserMenuItem icon={<Settings />} label="Settings" href="/settings" />
      <UserMenuItem icon={<LogOut />} label="Log out" destructive onClick={signOut} />
    </UserMenu>
  }
/>

Customize or replace

Every layer is swappable: trigger replaces the avatar button, children accepts anything (custom rows close the menu when they carry role="menuitem"), and open/onOpenChange hand you full control of the state. Don’t want the component at all? Avatar is exported standalone for building your own.

custom.tsx
// Replace the trigger entirely — the dropdown behavior stays.
<UserMenu
  username="Mara Kealoha"
  detail="Administrator"
  trigger={
    <span className="flex items-center gap-2 rounded-full border py-1 pl-1 pr-3 text-sm">
      <Avatar initials="MK" size="1.5rem" />
      Mara
    </span>
  }
>
  {/* any children work; clicks on role="menuitem" elements auto-close */}
  <ThemeSwitcherRow />
  <UserMenuItem label="Log out" destructive onClick={signOut} />
</UserMenu>

// In a Sidebar's bottom slot (the iX "infrastructure section" pattern):
<Sidebar variant="docked" bottomContent={
  <div className="p-2"><UserMenu username="Mara" initials="MK" align="start">…</UserMenu></div>
}>…</Sidebar>
The dropdown uses the same animate-in entrance utilities as HeaderNav — install tw-animate-css for the fade/zoom entrance (it degrades gracefully without it).

API

PropTypeDefaultDescription
username*string—Display name shown in the menu's info header.
detailstring—Secondary line under the name — an email, role, or tenant.
srcstring—Avatar image URL for the trigger and info header.
initialsstring—Avatar initials fallback.
triggerReactNode—Replace the default avatar button entirely.
openboolean—Controlled open state. Omit for uncontrolled.
onOpenChange(open: boolean) => void—Open-state change requests.
align"start" | "end""end"Horizontal alignment of the panel relative to the trigger.
childrenReactNode—Menu content — UserMenuItem elements or anything else. Clicks on role="menuitem" elements close the menu.
classNamestring—Extra classes for the dropdown panel.
triggerClassNamestring—Extra classes for the default trigger button.
aria-labelstring—Accessible name override for the trigger. Defaults to the "User menu" label.
PropTypeDefaultDescription
label*string—Row text.
iconReactNode—Leading 16px icon.
hrefstring—Renders the row as a link (through the LinkProvider component) instead of a button.
onClick() => void—Action handler.
destructivebooleanfalseStyle as a destructive action — log out, delete account.
classNamestring—Extra classes.
PropTypeDefaultDescription
srcstring—Image URL. Falls back to initials while missing or on load error.
altstring—Image alt text.
initialsstring—Shown when no image renders — keep it to 1–2 characters.
sizestring"2rem"Diameter as a CSS length.
classNamestring—Extra classes.