User Menu
The avatar button that opens the account menu: the person's name and email at the top, your own entries (settings, billing, help) in groups, a row for the theme, and a Sign out entry that can ask first, shows that it is working, and tells screen readers how it went.
A ready-made piece, built from the library's own parts.
A block puts several components together into something you would otherwise assemble by hand. It never fetches data: you give it the data, or answer its callbacks. It is copied into your project like any component, with the parts it uses, so you can change anything.
Basic
Sign out flow
Signed in
Permissions
ClassNames
Installation
npx rdloom add user-menuCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/user-menu.jsonWorks, but without upgrade tracking.
Usage
import { SettingsIcon, UserIcon, UserMenu } from "@rdloom/react";
<div className="flex justify-center p-8 pb-48">
<UserMenu
user={{ name: "Ada Lovelace", email: "ada@example.com" }}
items={[
{ id: "profile", label: "Your profile", icon: <UserIcon />, href: "/profile" },
{ id: "settings", label: "Settings", icon: <SettingsIcon />, href: "/settings" },
]}
onSignOut={async () => {
// Call your sign-out API here.
}}
/>
</div>API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
userrequiredThe signed-in person: name (also the accessible name), email and the picture. Without a picture the initials are shown. | { name: string; email?: string; avatarUrl?: string } | none |
itemsEntries above Sign out: { id, label, icon?, href?, onSelect?, permission?, variant? }. An entry with an href is a link; without one it is a button that calls onSelect. permission is "allow", "disabled", "hidden" or { state, reason }: hidden entries are not drawn, disabled ones are shown dimmed with the reason beside them. | UserMenuItem[] | none |
groupsMore entries in groups, each with an optional heading. A separator is drawn between groups, and before Sign out. | UserMenuGroup[] | none |
themeRowA row under the entries for choosing the theme, for example your segmented control. It sits below the menu inside the same popover and is reached with Tab. | node | none |
onSignOutAdds the Sign out entry and does the work. May be async. While it runs the entry shows it is pending; a throw or rejection is an error. Call your own sign-out API here; the library does not. | () => void | Promise<unknown> | none |
confirmAsk before signing out. Opens an alert dialog, for example { title: "Sign out of Loomworks?" }; its confirm button runs onSignOut. | { title: string; description?: string; confirmLabel?: string } | none |
signOutLabelText of the Sign out entry. | string | "Sign out" |
signingOutLabelText of the entry while the sign-out is running. | string | "Signing out..." |
signedOutMessageAnnounced politely when the sign-out succeeds. It is spoken from a status region that stays on the page after the menu closes. | string | "You have signed out" |
errorMessageAnnounced politely when the sign-out fails. | string | "Could not sign out. Try again." |
onSignedOutCalled after a successful sign-out, with what onSignOut returned. Usually where you go to the sign-in page. | (result: unknown) => void | none |
onErrorCalled with the error when onSignOut throws or rejects. | (error: unknown) => void | none |
permissionsWhat the person may do: { signOut } as "allow" (default), "disabled" or "hidden", or { state, reason }. The server must check again. | Permissions<"signOut"> | none |
sizeSize of the avatar button. | "sm" | "md" | "sm" |
alignWhich edge of the button the menu lines up with. | "start" | "end" | "end" |
showNameShow the name and email beside the picture in the button. Use it where there is room, such as a sidebar. | boolean | false |
labelStart of the button's accessible name. The person's name is added: "Account menu, Ada Lovelace". | string | "Account menu" |
classNamesClass names for the parts, by slot name, added after the built-in ones. Slots: root, trigger, avatar, name, popover, header, list, item, separator, theme, status. | Partial<Record<"root" | "trigger" | "avatar" | "name" | "popover" | "header" | "list" | "item" | "separator" | "theme" | "status", string>> | none |
Accessibility
Role button, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Enter, Space or Arrow Down on the button: opens the menu
- Arrow keys: move between entries; Home and End: first and last
- Enter or Space: chooses an entry
- Escape: closes the menu and returns focus to the button
- Tab (with a theme row): moves from the menu to the controls in the row
Screen readers announce
- "Account menu, Ada Lovelace, menu button, collapsed"
- "Ada Lovelace, menu", then each entry: "Settings, menu item"
- "Signing out..." while pending, then "You have signed out" or "Could not sign out. Try again." as a status message
What your code must do
- The button is named with the person's name ("Account menu, Ada Lovelace"); the picture is decorative
- The menu is a real menu named by the person's name; the entries are menu items or links
- Closing the menu returns focus to the button
- Sign out: while it runs the entry says so and a second choice is ignored; the result is announced politely from a status region that stays after the menu closes, not by moving focus
- With confirm, the alert dialog traps focus, starts on the safe choice, and focus returns to the button when it closes
- A disabled entry says why in text beside it, not only in a tooltip
- A destructive entry is shown in the danger color and also says what it does in words
Block contract
- Data
- The signed-in person (name, email, picture) and the menu entries as plain data.
- Data states
- None: it shows what it is given
- Permissions
- signOut, per menu item
- Events
- onSignOut, onSignedOut, onError, item onSelect
- You can replace
- themeRow slot; items and groups; classNames for each part; size, align, showName
Guidelines
Use it when
- The top-right corner of an app: who is signed in, account entries and Sign out
- At the bottom of a sidebar, with showName
Avoid it when
- A menu of actions on a record: use Menu
- Switching organisations or workspaces: use the team switcher of Sidebar
Don't
- Doing the sign-out yourself in onSelect of an item: use onSignOut so it gets pending, the message and the confirmation
- Putting more than about 7 entries in the menu
- UI permission is not security: hiding or disabling an entry only changes what people see, so the server must check again for every request
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-raised--rd-color-surface-subtle--rd-color-border-default--rd-color-text-default--rd-color-text-muted--rd-color-feedback-danger--rd-color-focus-ring--rd-radius-control--rd-radius-overlay--rd-elevation-floating