On this page
Import
import { Menubar } from '@envsemble/envui';
Usage
Menubar.Root, named by its aria-label, holds a Menubar.Menu for each menu.
- A Menubar.Menu takes its trigger's
label, such as File, and its items as children: Menu.Item, Menu.Submenu, Menu.Separator and Menu's other parts, with their shortcuts. disableddims one menu's trigger, or on Menubar.Root every menu.- A menu opens on its own, or with
open,defaultOpenandonOpenChange.
import { useRef, useState } from 'react';
import { Menu, Menubar, Column, Surface, Text, Textarea } from '@envsemble/envui';
import { IconContentCopy } from '@envsemble/envui/icons/ContentCopy';
import { IconHistory } from '@envsemble/envui/icons/History';
import { IconNoteAdd } from '@envsemble/envui/icons/NoteAdd';
import { IconSave } from '@envsemble/envui/icons/Save';
const recent = ['Roadmap', 'Interview notes', 'Launch checklist'];
export default function NotesMenubar() {
const note = useRef<HTMLTextAreaElement>(null);
// The field that had focus before the menubar took it, which Edit commands act on.
const editTarget = useRef<Element | null>(null);
const [lastCommand, setLastCommand] = useState('');
const [wordWrap, setWordWrap] = useState(true);
const field = () =>
editTarget.current instanceof HTMLTextAreaElement ? editTarget.current : note.current;
return (
<Column gap={3} style={{ inlineSize: 'min(100%, 28rem)' }}>
{/* Stands in for the application's window. */}
<Surface background="default" padding={3}>
<Column gap={3}>
<Menubar.Root aria-label="Notes menu" accessKeys>
<Menubar.Menu label="File" accessKey="F">
<Menu.Item
icon={<IconNoteAdd />}
shortcut="Ctrl+N"
onClick={() => setLastCommand('New note')}
>
New note
</Menu.Item>
<Menu.Submenu label="Open recent" icon={<IconHistory />}>
{recent.map((name) => (
<Menu.Item key={name} onClick={() => setLastCommand(`Opened ${name}`)}>
{name}
</Menu.Item>
))}
</Menu.Submenu>
<Menu.Separator />
<Menu.Item
icon={<IconSave />}
shortcut="Ctrl+S"
onClick={() => setLastCommand('Saved')}
>
Save
</Menu.Item>
</Menubar.Menu>
<Menubar.Menu
label="Edit"
accessKey="E"
onOpenChange={(open, { previousFocus }) => {
if (open) editTarget.current = previousFocus;
}}
>
<Menu.Item
icon={<IconContentCopy />}
shortcut="Ctrl+C"
onClick={() => {
const target = field();
if (!target) return;
const text = target.value.slice(target.selectionStart, target.selectionEnd);
void navigator.clipboard?.writeText(text).catch(() => undefined);
setLastCommand(text ? `Copied ${text.length} characters` : 'Nothing selected');
}}
>
Copy
</Menu.Item>
<Menu.Item
shortcut="Ctrl+A"
onClick={() => {
field()?.select();
setLastCommand('Selected all');
}}
>
Select all
</Menu.Item>
</Menubar.Menu>
<Menubar.Menu label="View" accessKey="V">
<Menu.CheckboxItem checked={wordWrap} onCheckedChange={setWordWrap}>
Word wrap
</Menu.CheckboxItem>
</Menubar.Menu>
<Menubar.Menu label="Help" accessKey="H">
<Menu.Item onClick={() => setLastCommand('Keyboard shortcuts')}>
Keyboard shortcuts
</Menu.Item>
</Menubar.Menu>
</Menubar.Root>
<Textarea
ref={note}
label="Note"
rows={4}
wrap={wordWrap ? 'soft' : 'off'}
defaultValue="Ship the pricing page on Friday. Ask Sam Lee to review the copy."
/>
</Column>
</Surface>
<Text variant="caption" emphasis="muted" role="status">
{lastCommand}
</Text>
</Column>
);
}Commands that edit a field
The menubar returns focus to where it was after a command. onOpenChange(open, details) receives details.previousFocus, the element that had focus before the menubar took it, so an Edit command such as Copy acts on that field.
<Menubar.Menu
label="Edit"
onOpenChange={(open, { previousFocus }) => {
if (open) editTarget.current = previousFocus;
}}
>
…
</Menubar.Menu>
Access keys
With accessKeys on Menubar.Root, holding Alt underlines each menu's accessKey, a letter of its label such as F for File.
- Alt with the letter opens that menu.
- Alt alone moves focus to the menubar and back.
- They are off by default; turn them on for one menubar per window.
- A menubar in an inert region, such as behind a modal dialog, takes none from outside it.
In a title bar
In a window that draws its own title bar, a TopBar with titleBar, put the menubar in its start, as the app frame pattern does in an installed app's window, and the InTitleBar example does.
<TopBar
titleBar
title="Roadmap.md"
start={
<Menubar.Root aria-label="Application menu">
<Menubar.Menu label="File">
<Menu.Item shortcut="Ctrl+N">New document</Menu.Item>
</Menubar.Menu>
</Menubar.Root>
}
/>
Composition
The menubar is env-menubar, with a trigger, env-menubar-trigger, for each menu, whose access key is env-menubar-access-key. Each menu's popup is the shared popup surface, env-menu; className, style and ref on Menubar.Menu reach it, and the trigger is the menubar's own. The arrow keys between menus follow the text direction.
Customize
Its menus are frosted, as every popup is: --env-popup-opacity, --env-popup-blur and --env-popup-shadow, set on a theme, change it, as Popover describes.
Accessibility
You provide
- An
aria-label, such as Application menu. - The keys behind every shortcut, and commands that act on
previousFocuswhere they edit a field.
envUI provides
- A
menubarwhose triggers aremenuitems witharia-haspopupandaria-expanded. aria-keyshortcutson triggers with access keys, such as Alt+F.- Focus that roves between triggers and returns to the earlier element after a command.
Props
Menubar.Root
An application menubar: a row of small text buttons that open menus, such as File, Edit, View and Help. Once a menu is open, hovering another trigger or pressing ← and → switches to its menu. Opening a menu neither locks scrolling nor shifts the page.
| Prop | Type | Description |
|---|---|---|
aria-labelrequired | string | Accessible name of the menubar, such as Application menu. |
childrenrequired | ReactNode | Menubar.Menu elements. |
accessKeysDefault false | boolean | Desktop access keys, off by default: holding Alt underlines each menu's accessKey, Alt with that letter opens the menu, and Alt alone moves focus to the menubar and back. Turn it on for one menubar per window. |
disabledDefault false | boolean | Disables every menu of the menubar. |
Also accepts the native props of <div>, such as ref, className and aria-* attributes.
Menubar.Menu
One menu of a Menubar.Root: a small text trigger and the menu that opens below it. After a command, focus returns to where it was before the menubar took it, as in a desktop application.
| Prop | Type | Description |
|---|---|---|
labelrequired | ReactNode | The trigger's text, such as File. |
accessKey | string | A letter of the label, such as F for File. With Menubar.Root's accessKeys, holding Alt underlines it and Alt with the letter opens the menu. |
childrenrequired | ReactNode | The menu's items: Menu.Item, Menu.Submenu, Menu.Separator and the other menu parts. |
aria-label | string | Accessible name of the menu; by default the label, when it is a string. |
disabledDefault false | boolean | The trigger stays in place, dimmed, but doesn't open its menu. |
open | boolean | Controlled visibility. |
defaultOpenDefault false | boolean | Whether an uncontrolled menu starts open; closed by default. |
onOpenChange | ( | Called when the menu opens or closes, with the element that had focus before the menubar took it. |
className | string | Class of the menu popup. |
ref | Ref< | The menu popup's ref; the trigger is the menubar's own. |
style | CSSProperties | The menu popup's inline style. |
Also accepts the native props of <div>, such as ref, className and aria-* attributes.
Part names
Each part's class and state attributes are public and versioned, for an application's CSS to style.
env-menubar- The menu bar.
env-menubar-access-key- The underlined access key in a trigger's label.
env-menubar-label- A trigger's label.
env-menubar-trigger- A button that opens a menu.
data-access-key- Any value, or present on env-menubar-trigger
data-access-keys-shown- Present
data-disabled- Any value, or present on env-menubar-trigger
data-popup-open- Any value, or present on env-menubar-trigger
Changelog
- 1.0.0
The parts are members of the
Menubarnamespace:MenubarisMenubar.Root,MenubarMenuisMenubar.Menu, andMenubarMenuOpenChangeDetailsisMenubar.MenuFocusDetails. Menubar.Menu's popup is the shared popup surface and takesrefandstyle; the arrow keys between menus follow the text direction.- 1.0.0-prerelease.6
Colors under the pointer and while pressed change with
--env-motion-ease-enter, rather than easing in slowly, so hover and press show from the first frame.A menu opens over
--env-motion-duration-medium, fading in and growing from 98 % with--env-motion-ease-enter, rather than growing from 96 % over the slow duration, so it shows from its first frames rather than late.- 1.0.0-prerelease.5
A Menubar's menus are frosted: the popup's color lets a little of what is behind through, blurred, with a soft shadow and no outline edge but a faint hairline in dark, and is solid with its edge at high contrast and with reduced transparency, with Menu's new rows.