Account Menu
Give signed-in users a menu for their account, workspaces, theme and sign-out action.
import { AccountMenu, AccountMenuUser, AccountMenuWorkspace } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use it at the end of an App Header or at the foot of a Sidebar: the one place a person finds their profile, settings, theme and the way out. It's a Dropdown Menu with the account parts already laid out, so build your own dropdown when the menu isn't about the signed-in user. When switching workspaces is a main job in the app, give it a Workspace Switcher of its own.
import {
AccountMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuShortcut,
type AccountMenuUser,
} from '@youj/react'
export function Account({ user, onSignOut }: { user: AccountMenuUser; onSignOut: () => void }) {
return (
<AccountMenu user={user} onSignOut={onSignOut}>
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</AccountMenu>
)
}The trigger is the user's avatar, with their initials until the image loads (or if it fails). The menu opens with a profile header: a larger avatar, the name and the email, each truncated when it runs long. Then, in order and each behind a separator:
- Workspaces, when you pass
workspaces: a radio group of logos and names, with a check onworkspace.onWorkspaceChangereports a choice. - Your items:
children, any Dropdown Menu rows and groups. - Theme, when you pass
themeorcolorMode: a submenu with the built-in presets, each wearing its accent, and an appearance choice of light, dark or system. - Sign out, when you pass
onSignOut.
Description
An avatar menu with workspaces, theme and appearance choices, and a sign-out action; choosing a theme or mode restyles the card beside it.
The Theme submenu lists every preset @youj/core ships (themes), and the current one's name sits beside the row. The menu only reports what was chosen. Apply it where your app keeps its theme, with a Youj Provider or, as the preview does, a ThemeScope around part of the page:
import { useState } from 'react'
import { AccountMenu, type AccountMenuUser } from '@youj/react'
import { ThemeScope, presetById, type ColorMode } from '@youj/core'
export function ThemedAccount({ user }: { user: AccountMenuUser }) {
const [theme, setTheme] = useState('basalt')
const [mode, setMode] = useState<ColorMode>('dark')
return (
<ThemeScope theme={presetById(theme)}>
<AccountMenu user={user} theme={theme} onThemeChange={setTheme} colorMode={mode} onColorModeChange={setMode} />
</ThemeScope>
)
}Pass only colorMode for an appearance choice without presets, or only theme for presets without it.
A menu with only a user and a sign-out action, using a very long name and address.
With just a user and onSignOut, it's who you are and a way out. Long names and addresses truncate instead of widening the menu.
align and side place the menu against the avatar. It aligns to the end by default, which suits the corner of a header. Choose side="top" at the foot of a sidebar.
Code
account-menu-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsuser, workspaces, workspace… (14)
Props
| Prop | Type | |
|---|---|---|
userRequired | AccountMenuUser | – |
workspaces | AccountMenuWorkspace[]Workspaces to switch between, shown as a radio group. | – |
workspace | string | – |
onWorkspaceChange | (id: string) => void | – |
theme | stringThe active preset's id ( | – |
onThemeChange | (id: string) => void | – |
colorMode | ColorMode'system''light''dark' | – |
onColorModeChange | (mode: ColorMode) => void | – |
onSignOut | () => voidAdds Sign out at the end. | – |
children | ReactNodeYour own rows, between the profile and the theme: DropdownMenuItem, DropdownMenuShortcut, groups. | – |
align | Align'end''start''center'default 'end' | 'end' |
side | Side'left''right''top''bottom''inline-end''inline-start' | – |
trigger | ReactElement<Record<string, unknown>>Your own trigger in place of the avatar, e.g. a sidebar's account card or a status pill: an element the trigger renders as, with its content inside. It keeps the menu's accessible name unless it brings its own. | – |
className | stringThe trigger's class. | – |
MotionOpen, close, highlight… · reduced motion
Motion
The motion is the Dropdown Menu's.
| Moment | Tokens | ||
|---|---|---|---|
Opengrows out of the avatar: scale .96 → 1 and fade (yj-pop) | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
| Close | duration.instantease.exit80ms · cubic-bezier(0.4, 0, 1, 1) | 80mscubic-bezier(0.4, 0, 1, 1) | |
| Highlightone Sliding Indicator glides between rows | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 | |
| Checkdraws in on the chosen workspace, preset or appearance | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| Trigger ringappears around the avatar while the menu is open | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Theme and Sign out iconsplay as their row is highlighted | no token per icon | per icon |
Reduced motion
The menu and its submenu appear at once, the highlight jumps between rows, and checks appear already drawn.
Reduced motion
The menu opens and closes at once, the highlight moves from row to row without gliding, and checks appear already drawn. Keyboard handling and submenus work the same.
AccessibilityKeyboard, focus, announcements
Accessibility
- The trigger is a button named "Account: " and the user's name. The avatars inside are decorative.
- Everything else is the Dropdown Menu's: roving focus with ↑ and ↓, typeahead, Home and End, and Escape to close and return focus to the avatar.
- The Theme row opens its submenu on → (← to come back), mirrored in right-to-left layouts. Workspaces, presets and appearance are radio groups, so the chosen one is announced as checked.
- Each preset's color dot is decorative; its name is the label.