User menu

Your account, settings, theme, and sign out behind the avatar. Opens as a bottom sheet on phones.

npm install https://animui.stracture.net/__local/package.tgz
Live · keyboard ready

  • The single account entry point in an app top bar.
  • Menus that combine presence status, theme preference, account links, and sign out.
  • Apps that want async sign out with a spinner in the menu.
  • Use dropdown-menu for generic command lists.
  • Use theme-switch for a standalone light and dark toggle.
  • Use workspace-sidebar when account and workspace switching live in a side navigation.

Installation

Install animui once, then import any component or block from its typed package entry.

animui · 250 components and blocks

React 19 and React DOM 19 are required. The package includes Free and Pro entries, styles and TypeScript declarations.

npm install https://animui.stracture.net/__local/package.tgz

Load the stylesheet once at your app root, then use the typed component import:

import 'animui/styles.css';
import { PresenceDot } from 'animui/user-menu';

Read the Code tab for the implementation files and the API reference below for required props.

Download animui 0.3.0 · Agent guide
example.tsx
import { UserMenu } from "animui/user-menu";import { CreditCard, Settings, UserRound } from "lucide-react"; export function TopBarAccount({ user }: { user: { name: string; email: string } }) {  return (    <UserMenu      user={{ ...user, plan: "Pro" }}      showName      onThemeChange={setThemePreference}      items={[        { label: "Profile", icon: <UserRound size={16} />, onSelect: openProfile },        { label: "Settings", icon: <Settings size={16} />, keys: ["⌘", ","], onSelect: openSettings },        { label: "Billing", icon: <CreditCard size={16} />, onSelect: openBilling },      ]}      onSignOut={() => signOut()}    />  );}

API reference

2 parts. The first is the root.

UserMenu

Account menu behind an avatar trigger: a compact identity header, a short group of account links, an inline theme switch, and sign out. Below 640px it opens as a bottom sheet.

PropTypeDefaultDescription
userRequiredUserMenuUser–{ name, email, plan?, avatarSrc?, avatarSrcSet? }. Falls back to initials without a photo. plan shows as a small accent badge.
itemsUserMenuItem[][]Account destinations: { label, icon?, keys?, onSelect? }. Keep it to three or four. keys show quiet shortcut hints such as ["⌘", ","].
showNamebooleanfalseShows the name and a chevron beside the avatar from 640px up. Phones always show the avatar alone.
theme"light" | "dark" | "system"–Controlled theme preference.
defaultTheme"light" | "dark" | "system""system"Initial theme when uncontrolled.
onThemeChange(theme: ThemePreference) => void–Reports the choice. Applying it to the page is up to the app. The menu stays open.
showThemebooleantrueShows the inline light, dark, and system switch.
status"available" | "busy" | "away"–Controlled presence status. Passing status or onStatusChange adds the presence dot and an inline status switch.
defaultStatus"available" | "busy" | "away""available"Initial status when uncontrolled.
onStatusChange(status: UserStatus) => void–Called when a status is chosen. The menu stays open.
onSignOut() => void | Promise<unknown>–Sign out handler. A returned promise keeps the menu open with a spinner until it settles.
signOutKeysstring[]–Shortcut hint for the sign out item.
openboolean–Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled. A menu that starts open does not take focus.
onOpenChange(open: boolean) => void–Called when the menu opens or closes.
align"start" | "center" | "end""end"Panel alignment against the trigger.
portalbooleantrueRenders the desktop panel on the body. Pass false to keep it inside the trigger wrapper, for example in a contained preview. The mobile sheet always uses the body.
refRef<HTMLButtonElement>–The trigger button, for returning focus.
classNamestring–Extra class on the trigger.

PresenceDot

The status mark on its own, for avatars elsewhere in the app. Each status has its own shape as well as color.

PropTypeDefaultDescription
statusRequired"available" | "busy" | "away" | "offline"–Status to show.
classNamestring–Extra class for positioning.
EnterorSpaceorArrowDown
Opens the menu from the avatar and moves to the first row.
ArrowUp
Opens the menu from the avatar and moves to the last row.
ArrowDownorArrowUp
Moves between rows, looping at the ends. The highlight follows.
HomeorEnd
Moves to the first or last row.
A to Z
Typeahead jumps to the next row starting with the typed letters.
ArrowLeftorArrowRight
Chooses the previous or next option in the theme or status switch.
EnterorSpace
Activates the row, or chooses a theme or status without closing.
EscapeorTab
Closes the menu and returns focus to the avatar.
  • The trigger has aria-haspopup="menu" and aria-expanded, and is labelled "Account menu, <name>" plus the status when one is shown.
  • Rows are menuitem buttons; the theme and status switches are labelled groups of menuitemradio buttons with aria-checked.
  • Pointer focus draws no ring; keyboard focus shows animui's shared focus ring. The highlight marks the keyboard position. Outside press, the scrim, and swipe down close the menu.
  • Sign out sets aria-busy while an async handler runs; shortcut hints are aria-hidden.
  • The panel scales and fades out of the avatar on a spring that never overshoots; rows fade in with a short stagger, and closing is a quick fade.
  • One highlight glides between rows for the pointer and the keyboard; the switch thumb slides between options.
  • On phones the sheet rises from the bottom edge and follows a downward drag, closing past a short distance or a quick flick.
  • Reduced motion opens and closes with a short fade, jumps the highlight, and turns off dragging.
  • The menu is 17.5rem wide, capped at the viewport width minus 24px, with 12px collision padding.
  • Long names and emails ellipsize instead of widening the menu.
  • The trigger is a fixed 40px avatar, and the theme row keeps a 44px minimum height for touch.
  • Content renders only while open; each opening gets a fresh layout namespace so shared indicators do not animate from stale positions.
  • The avatar image decodes async and falls back to initials on error.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use as the single account entry point in an app top bar. For generic command lists use dropdown-menu; for a standalone light/dark toggle use theme-switch.
  • Keep items to three or four account destinations; the theme switch and sign out are built in.
  • onThemeChange only reports; apply the theme to the document yourself, optionally with a view transition.
  • Return the sign out promise so the item shows progress and the menu closes when it settles.
/components/user-menu/markdown

The full library index for assistants is at /llms.txt.