Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Templates

    Account menu

    A menu opened by the avatar and name at the end of the top bar; profile and settings links, workspace and theme submenus, help and sign-out.

    The account menu gathers the person’s own links and preferences in one place. The AppShell template puts it at the very end of the top bar; on narrow screens the name hides and the avatar stays.

    Press the name at the end of the top bar. Move with the arrow keys; the right arrow opens the “Workspace” and “Theme” submenus. In the demo, “Sign out” shows a toast. The brand and mode switches in the page header restyle the preview too.

    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Terminal window
    pnpm add @bigbang/ui
    Data Type Note
    Person { name, email } The name on the button and avatar, the email as the group label.
    Workspace string When it changes, the app opens that workspace; the demo shows a toast.
    onSignOut () => void The app signs out.
    import { Avatar, Button, DropdownMenu, Icon, useToast } from '@bigbang/ui';
    import { useState } from 'react';
    import type { Lang } from '../../patterns/lang';
    const copy = {
    tr: {
    name: 'Kullanıcı A',
    trigger: 'Hesap menüsü: Kullanıcı A',
    account: 'kullanici.a@ornek.com',
    profile: 'Profil',
    settings: 'Ayarlar',
    workspace: 'Çalışma alanı',
    workspaces: ['Çalışma alanı 1', 'Çalışma alanı 2'],
    switched: (name: string) => `${name} açıldı.`,
    theme: 'Tema',
    themes: { light: 'Açık', dark: 'Koyu', system: 'Sistem' },
    help: 'Yardım',
    signOut: 'Çıkış yap',
    signedOut: 'Oturum kapatıldı.',
    },
    en: {
    name: 'User A',
    trigger: 'Account menu: User A',
    account: 'user.a@example.com',
    profile: 'Profile',
    settings: 'Settings',
    workspace: 'Workspace',
    workspaces: ['Workspace 1', 'Workspace 2'],
    switched: (name: string) => `${name} opened.`,
    theme: 'Theme',
    themes: { light: 'Light', dark: 'Dark', system: 'System' },
    help: 'Help',
    signOut: 'Sign out',
    signedOut: 'Signed out.',
    },
    };
    export type AccountMenuProps = {
    lang?: Lang;
    /** Opens the menu on first render. */
    defaultOpen?: boolean;
    /** Runs on "Sign out". The demo shows a toast. */
    onSignOut?: () => void;
    };
    /**
    * The account menu at the end of the app header: the person's avatar and name open a DropdownMenu
    * with profile and settings links, a workspace submenu, a theme choice, help and sign-out.
    */
    export function AccountMenu({ lang = 'tr', defaultOpen = false, onSignOut }: AccountMenuProps) {
    const t = copy[lang];
    const toast = useToast();
    const [workspace, setWorkspace] = useState(t.workspaces[0] ?? '');
    const [theme, setTheme] = useState('system');
    return (
    <DropdownMenu
    align="end"
    defaultOpen={defaultOpen}
    trigger={
    <Button variant="tertiary" size="sm" aria-label={t.trigger} startIcon={<Avatar name={t.name} size="xs" decorative />} endIcon={<Icon name="chevron-down" />}>
    <span className="hidden sm:inline">{t.name}</span>
    </Button>
    }
    >
    <DropdownMenu.Group label={t.account}>
    <DropdownMenu.LinkItem href="#" icon="user">
    {t.profile}
    </DropdownMenu.LinkItem>
    <DropdownMenu.LinkItem href="#" icon="settings">
    {t.settings}
    </DropdownMenu.LinkItem>
    </DropdownMenu.Group>
    <DropdownMenu.Separator />
    <DropdownMenu.Submenu label={t.workspace} icon="layout-grid">
    <DropdownMenu.RadioGroup
    label={t.workspace}
    value={workspace}
    onValueChange={(value) => {
    setWorkspace(value);
    toast.show({ title: t.switched(value) });
    }}
    >
    {t.workspaces.map((name) => (
    <DropdownMenu.RadioItem key={name} value={name}>
    {name}
    </DropdownMenu.RadioItem>
    ))}
    </DropdownMenu.RadioGroup>
    </DropdownMenu.Submenu>
    <DropdownMenu.Submenu label={t.theme} icon="sun">
    <DropdownMenu.RadioGroup label={t.theme} value={theme} onValueChange={setTheme}>
    {(['light', 'dark', 'system'] as const).map((value) => (
    <DropdownMenu.RadioItem key={value} value={value}>
    {t.themes[value]}
    </DropdownMenu.RadioItem>
    ))}
    </DropdownMenu.RadioGroup>
    </DropdownMenu.Submenu>
    <DropdownMenu.LinkItem href="#" icon="circle-help">
    {t.help}
    </DropdownMenu.LinkItem>
    <DropdownMenu.Separator />
    <DropdownMenu.Item icon="log-out" onSelect={onSignOut ?? (() => toast.show({ title: t.signedOut }))}>
    {t.signOut}
    </DropdownMenu.Item>
    </DropdownMenu>
    );
    }
    1. Trigger: a tertiary Button with a decorative Avatar first and a down chevron last.
    2. DropdownMenu (align="end").
    3. A group labelled with the email: Profile and Settings links (DropdownMenu.LinkItem).
    4. “Workspace” and “Theme” submenus, each a single-choice RadioGroup.
    5. A Help link, a separator and “Sign out” last.

    The menu fades and scales in under the trigger with motion.popover; submenus open beside it. With reduced motion only a short fade stays.

    • The trigger is named “Account menu: User A”; the visible name is part of it (label in name). The avatar is decorative, so the name is not read twice.
    • The menu has the menu role; arrow keys, Home/End and typing a letter move through it. Submenu items carry aria-haspopup="menu"; the right arrow opens and the left arrow closes them.
    • Theme and workspace options are menuitemradio; the chosen one is read through aria-checked.
    • Escape closes the menu and returns focus to the trigger; choosing an item returns focus there too.
    • Links are real a elements, so they open in a new tab.

    DropdownMenu, Avatar, Button, Icon, Toast.