Account and organizations

Show who is signed in and where, from a menu in the frame.

BetaSince 1.0.0
On this page

Import

import {
  Avatar,
  AvatarButton,
  Button,
  Column,
  IconButton,
  Menu,
  Row,
  Text,
  ThemeMenuRadioGroup,
  TopBar,
} from '@envsemble/envui';

Usage

A Menu.Root named aria-label="Account", with align="end", takes as its trigger an AvatarButton with the person's name and, for its tooltip, their email as description.

  • A Menu.Header holds a large Avatar, size="lg", centred above the name and email; it is not an item, so focus starts on the first item.
  • Then come the items, such as a Menu.Item with href="/account", a Menu.Submenu labelled Theme, and Sign out after a Menu.Separator.
AvatarMenu.tsx
import {
  Avatar,
  AvatarButton,
  Column,
  Menu,
  Text,
  ThemeMenuRadioGroup,
  TopBar,
} from '@envsemble/envui';
import { IconLogout } from '@envsemble/envui/icons/Logout';
import { IconManageAccounts } from '@envsemble/envui/icons/ManageAccounts';
import { IconPalette } from '@envsemble/envui/icons/Palette';

const user = { name: 'Alex Morgan', email: 'alex.morgan@example.com' };

export default function AvatarMenu() {
  return (
    <TopBar
      title="Northwind"
      actions={
        // The avatar at the end of the top bar opens the menu, which lines up with its end.
        <Menu.Root
          aria-label="Account"
          align="end"
          trigger={<AvatarButton name={user.name} description={user.email} />}
        >
          {/* Who is signed in, centered above the person's own things. The header is not an
              item, so focus starts on Account settings and the menu's label still names it. */}
          <Menu.Header>
            <Column align="center" gap={1} padding={2} style={{ textAlign: 'center' }}>
              <Avatar name={user.name} size="lg" />
              <Text>{user.name}</Text>
              <Text variant="body-sm" emphasis="muted">
                {user.email}
              </Text>
            </Column>
          </Menu.Header>
          <Menu.Item href="/account" icon={<IconManageAccounts />}>
            Account settings
          </Menu.Item>
          <Menu.Submenu label="Theme" icon={<IconPalette />}>
            <ThemeMenuRadioGroup />
          </Menu.Submenu>
          <Menu.Separator />
          <Menu.Item icon={<IconLogout />}>Sign out</Menu.Item>
        </Menu.Root>
      }
    />
  );
}

The theme

A ThemeMenuRadioGroup inside the Theme Menu.Submenu applies the choice at once and keeps the menu open; the nearest ThemePreferenceProvider stores it.

Organizations in the account menu

A Menu.Submenu labelled Organization, with the current one as its description, holds a Menu.RadioGroup with value and onValueChange and a Menu.RadioItem for each organization.

<Menu.Submenu label="Organization" icon={<IconGroups3 />} description={current?.label}>
  <Menu.RadioGroup value={organization} onValueChange={setOrganization}>
    {organizations.map(({ id, label }) => (
      <Menu.RadioItem key={id} value={id}>
        {label}
      </Menu.RadioItem>
    ))}
  </Menu.RadioGroup>
</Menu.Submenu>

A switcher of its own

A Menu.Root named by aria-label="Organizations", with align="start", takes a secondary Button as its trigger, whose label is the active organization's name and whose endIcon is UnfoldMore.

  • Its organizations are a Menu.RadioGroup whose value is the active one; onValueChange switches.
  • A role="status" message reports a switch that takes time.
  • With no organizations, render a secondary Button with the Add startIcon that creates one, in place of the Menu.
<Menu.Root
  aria-label="Organizations"
  align="start"
  trigger={
    <Button variant="secondary" endIcon={<IconUnfoldMore />}>
      {active.label}
    </Button>
  }
>
  <Menu.RadioGroup aria-labelledby={headingId} value={current} onValueChange={switchTo}>
    <Menu.RadioItem value="northwind" description="Admin">
      Northwind Labs
    </Menu.RadioItem>
  </Menu.RadioGroup>
</Menu.Root>

A row at the foot of the navigation

The trigger is a Button with variant="ghost", fullWidth and an endIcon, holding a small Avatar, size="sm", and the name in a Row. The menu opens above it with side="top" and align="start".

<Menu.Root
  aria-label="Account"
  side="top"
  align="start"
  trigger={
    <Button variant="ghost" fullWidth endIcon={<IconUnfoldMore />}>
      <Row align="center" gap={2}>
        <Avatar name="Priya Shah" size="sm" />
        <Text>Priya Shah</Text>
      </Row>
    </Button>
  }
>
  <Menu.Item icon={<IconLogout />}>Sign out</Menu.Item>
</Menu.Root>

Composition

The switcher's first row is a Menu.Header, not an item: a centred Column with IconGroups3 above a headline Text, whose id names the radio group through aria-labelledby. Each organization is a Menu.RadioItem with the person's role as its description. A Menu.Separator and a Menu.Item with IconAdd end the menu with the create action. In a NavigationDrawer the switcher goes in its header.

The parts are the trigger, env-button, the heading row, env-menu-header, each organization, env-menu-item, its role, env-menu-item-description, and the create action, env-menu-separator + env-menu-item.

Accessibility

You provide

  • Each menu's name, Account or Organizations, and the person's name and email.
  • What Sign out does, the account settings page, and the switch itself, with a status where it takes time.

envUI provides

  • The roles, states and focus handling of Menu, its submenus and radio items: the organizations as a radio group named by the heading, the active one checked.
  • An AvatarButton named by the person's name, with their description in its tooltip.
  • A Menu.Header that is not an item: arrow keys skip it, focus starts on the first item, and the menu keeps its own name.

Changelog

1.0.0

The account menu and the organization switcher are one pattern, built from Menu, Avatar and Button, in place of the AccountMenu and OrganizationSwitcher components. The page is on the figure template.