Menubar

Desktop application style menubar with a row of menu triggers

Usage

Menubar is a desktop-application style menu bar: a horizontal row of top-level menu triggers (File, Edit, View, …) where each trigger opens a dropdown. Arrow keys move between the top-level menus, and once one menu is opened, moving to a sibling opens it immediately. Menubar follows the WAI-ARIA menubar pattern.

Menubar is built on top of Menu – each Menubar.Menu is a separate Menu instance, and the dropdown content is composed from the usual Menu.Item, Menu.Divider, Menu.Label, Menu.Sub, Menu.CheckboxItem and Menu.RadioItem components.

import { Menu, Menubar, Text } from '@mantine/core';

function Demo() {
  return (
    <Menubar>
      <Menubar.Menu width={220}>
        <Menubar.Target>File</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item rightSection={<Text size="xs" c="dimmed">⌘N</Text>}>New file</Menu.Item>
          <Menu.Item rightSection={<Text size="xs" c="dimmed">⌘⇧N</Text>}>New window</Menu.Item>
          <Menu.Sub>
            <Menu.Sub.Target>
              <Menu.Sub.Item>Open recent</Menu.Sub.Item>
            </Menu.Sub.Target>
            <Menu.Sub.Dropdown>
              <Menu.Item>project-alpha</Menu.Item>
              <Menu.Item>project-beta</Menu.Item>
              <Menu.Item>project-gamma</Menu.Item>
            </Menu.Sub.Dropdown>
          </Menu.Sub>
          <Menu.Divider />
          <Menu.Item rightSection={<Text size="xs" c="dimmed">⌘S</Text>}>Save</Menu.Item>
          <Menu.Item>Save as…</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu width={220}>
        <Menubar.Target>Edit</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item rightSection={<Text size="xs" c="dimmed">⌘Z</Text>}>Undo</Menu.Item>
          <Menu.Item rightSection={<Text size="xs" c="dimmed">⌘⇧Z</Text>}>Redo</Menu.Item>
          <Menu.Divider />
          <Menu.Item>Cut</Menu.Item>
          <Menu.Item>Copy</Menu.Item>
          <Menu.Item>Paste</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu width={220}>
        <Menubar.Target>Help</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>Documentation</Menu.Item>
          <Menu.Item>Keyboard shortcuts</Menu.Item>
          <Menu.Item>About</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>
    </Menubar>
  );
}

How it differs from Menu

Menu is a single disclosure widget – one target that toggles one dropdown. Menubar coordinates multiple menus:

  • The root renders role="menubar" and Menubar.Target renders role="menuitem" triggers with aria-haspopup="menu".
  • Only one menu in the bar is open at a time. Moving to a sibling while a menu is open switches which menu is open.
  • The whole bar is a single tab stop (roving tabindex) – Tab moves focus into and out of the menu bar as a single unit, arrow keys move between triggers.

If you need a single dropdown attached to one button, use Menu instead.

Composition

Menubar consists of the following components:

  • Menubar – root element, owns the open/active state of all menus
  • Menubar.Menu – wraps a single menu, renders a Menu instance under the hood
  • Menubar.Target – top-level trigger button (role="menuitem")
  • Menubar.Dropdown – dropdown container, accepts the same children as Menu.Dropdown
import { Menu, Menubar } from '@mantine/core';

function Demo() {
  return (
    <Menubar>
      <Menubar.Menu>
        <Menubar.Target>File</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>New file</Menu.Item>
          <Menu.Item>Open…</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu>
        <Menubar.Target>Edit</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>Undo</Menu.Item>
          <Menu.Item>Redo</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>
    </Menubar>
  );
}

Trigger and loop

trigger controls how a menu is opened when none of the menus is currently open:

  • click (default) – a menu opens when its target is clicked. Once any menu is open, hovering a sibling target switches to it immediately. This matches the behavior of native desktop application menu bars.
  • hover – a menu opens as soon as its target is hovered, even when all menus are closed.

With trigger="hover", a menu opens as soon as its target is hovered and closes when the pointer leaves the bar:

import { Menu, Menubar } from '@mantine/core';

function Demo() {
  return (
    <Menubar trigger="hover">
      <Menubar.Menu width={220}>
        <Menubar.Target>File</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>New file</Menu.Item>
          <Menu.Item>New window</Menu.Item>
          <Menu.Item>Save</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu width={220}>
        <Menubar.Target>Edit</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>Undo</Menu.Item>
          <Menu.Item>Redo</Menu.Item>
          <Menu.Item>Cut</Menu.Item>
          <Menu.Item>Copy</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu width={220}>
        <Menubar.Target>View</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>Zoom in</Menu.Item>
          <Menu.Item>Zoom out</Menu.Item>
          <Menu.Item>Reset zoom</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>
    </Menubar>
  );
}

loop (default true) controls whether arrow key navigation wraps around from the last menu to the first and vice versa.

Submenus

Use Menu.Sub inside Menubar.Dropdown to create nested submenus. ArrowRight opens a submenu and moves focus to its first item, ArrowLeft closes it and returns focus to the parent item. See the Menu submenus documentation for more information.

The usage demo above includes a submenu in the File menu.

Checkbox and radio items

Menubar.Dropdown supports the same selectable items as Menu: Menu.CheckboxItem for toggles and Menu.RadioGroup with Menu.RadioItem for single-choice options. By default, clicking a checkbox or radio item does not close the menu.

import { Menu, Menubar } from '@mantine/core';

function Demo() {
  return (
    <Menubar>
      <Menubar.Menu width={220}>
        <Menubar.Target>View</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.CheckboxItem defaultChecked>Show sidebar</Menu.CheckboxItem>
          <Menu.CheckboxItem>Show status bar</Menu.CheckboxItem>
          <Menu.Divider />
          <Menu.Label>Appearance</Menu.Label>
          <Menu.RadioGroup defaultValue="comfortable">
            <Menu.RadioItem value="compact">Compact</Menu.RadioItem>
            <Menu.RadioItem value="comfortable">Comfortable</Menu.RadioItem>
            <Menu.RadioItem value="spacious">Spacious</Menu.RadioItem>
          </Menu.RadioGroup>
        </Menubar.Dropdown>
      </Menubar.Menu>

      <Menubar.Menu width={220}>
        <Menubar.Target>Window</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item>Minimize</Menu.Item>
          <Menu.Item>Zoom</Menu.Item>
          <Menu.Item>Bring all to front</Menu.Item>
        </Menubar.Dropdown>
      </Menubar.Menu>
    </Menubar>
  );
}

Controlled

Set openIndex and onOpenChange to control which menu is open. openIndex is the zero-based index of the opened Menubar.Menu (in DOM order) or null when all menus are closed:

import { useState } from 'react';
import { Button, Group, Menu, Menubar, Stack } from '@mantine/core';

function Demo() {
  const [openIndex, setOpenIndex] = useState<number | null>(null);

  return (
    <Stack>
      <Group>
        <Button variant="default" onClick={() => setOpenIndex(0)}>Open File</Button>
        <Button variant="default" onClick={() => setOpenIndex(1)}>Open Edit</Button>
        <Button variant="default" onClick={() => setOpenIndex(null)}>Close all</Button>
      </Group>

      <Menubar openIndex={openIndex} onOpenChange={setOpenIndex}>
        <Menubar.Menu width={220}>
          <Menubar.Target>File</Menubar.Target>
          <Menubar.Dropdown>
            <Menu.Item>New file</Menu.Item>
            <Menu.Item>Save</Menu.Item>
          </Menubar.Dropdown>
        </Menubar.Menu>

        <Menubar.Menu width={220}>
          <Menubar.Target>Edit</Menubar.Target>
          <Menubar.Dropdown>
            <Menu.Item>Undo</Menu.Item>
            <Menu.Item>Redo</Menu.Item>
          </Menubar.Dropdown>
        </Menubar.Menu>
      </Menubar>
    </Stack>
  );
}

For uncontrolled usage with an initially open menu, use the defaultOpenIndex prop instead.

Position

By default, dropdowns are positioned at bottom-start relative to their target. Change the position prop to use a different Floating UI position for all menus in the bar:

import { Menubar } from '@mantine/core';

function Demo() {
  return (
    <Menubar position="bottom-end">
      {/* ...menus */}
    </Menubar>
  );
}

Individual menus accept the same props as Menu – pass them to Menubar.Menu to override settings for a single menu, for example position, withinPortal, closeOnItemClick, shadow, width or transitionProps.

Keyboard interactions

Menubar implements the keyboard interactions defined by the WAI-ARIA menubar pattern. When RTL direction is set, ArrowLeft and ArrowRight are swapped.

KeyDescription
ArrowRightMove to the next top-level menu, switch the open menu if one is open
ArrowLeftMove to the previous top-level menu, switch the open menu if one is open
ArrowDown / Enter / SpaceOpen the focused menu and move focus to its first item
ArrowUpOpen the focused menu and move focus to its last item
EscapeClose the open menu and return focus to its target
Home / EndMove to the first / last top-level menu
Character keysMove focus to the next top-level menu that starts with the typed character
TabMove focus into or out of the menu bar (single tab stop)

Styles API

Menubar supports the Styles API; you can add styles to any inner element of the component with the classNames prop. Follow the Styles API documentation to learn more.

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */

Accessibility

Menubar follows the WAI-ARIA menubar pattern:

  • The root element has role="menubar" and aria-orientation="horizontal".
  • Each Menubar.Target has role="menuitem", aria-haspopup="menu" and aria-expanded.
  • The menu bar is a single tab stop – only the active trigger is included in the tab sequence, arrow keys move focus between the triggers.

The labels you pass to Menubar.Target are used as accessible names for the triggers, so make sure they describe the menu content.