Menubar

데스크톱 애플리케이션 스타일의 메뉴 트리거 행으로 된 메뉴 바 컴포넌트예요. 가로로 배열된 최상위 메뉴 트리거(File, Edit, View 등)를 표시하며, 각 트리거가 드롭다운을 열어요.

출처: 문서

본문

사용법 (Usage)

Menubar은 데스크톱 애플리케이션 스타일의 메뉴 바예요. 최상위 메뉴 트리거(File, Edit, View 등)의 가로 행으로, 각 트리거가 드롭다운을 열어요. 화살표 키로 최상위 메뉴 사이를 이동하고, 메뉴가 열린 상태에서 형제 메뉴로 이동하면 즉시 그 메뉴가 열려요. Menubar은 WAI-ARIA menubar 패턴을 따릅니다.

Menubar은 Menu 위에 구축돼요. 각 Menubar.Menu는 별도의 Menu 인스턴스이고, 드롭다운 콘텐츠는 일반적인 Menu.Item, Menu.Divider, Menu.Label, Menu.Sub, Menu.CheckboxItem, Menu.RadioItem 컴포넌트로 구성돼요.

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

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

      <Menubar.Menu>
        <Menubar.Target>Edit</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.Item rightSection={<Text size="xs">⌘Z</Text>}>Undo</Menu.Item>
          <Menu.Item rightSection={<Text size="xs">⌘⇧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>
        <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>
  );
}

Menu는 하나의 디스클로저 위젯이에요. 하나의 타겟이 하나의 드롭다운을 토글해요. Menubar은 여러 메뉴를 조정해요.

  • 루트는 role="menubar"를 렌더링하고, Menubar.Target은 aria-haspopup="menu"가 있는 role="menuitem" 트리거를 렌더링해요.
  • 바 안에서 한 번에 하나의 메뉴만 열려요. 메뉴가 열린 상태에서 형제로 이동하면 어떤 메뉴가 열릴지 전환돼요.
  • 바 전체가 하나의 탭 스톱(roving tabindex)이에요. Tab은 메뉴 바 안팎으로 하나의 단위로 포커스를 이동하고, 화살표 키로 트리거 사이를 이동해요.

하나의 버튼에 연결된 단일 드롭다운이 필요하면 Menu를 사용해요.

구성 (Composition)

Menubar은 다음 컴포넌트로 구성돼요.

  • Menubar – 루트 요소, 모든 메뉴의 열림/활성 상태를 관리해요
  • Menubar.Menu – 단일 메뉴를 감싸고, 내부적으로 Menu 인스턴스를 렌더링해요
  • Menubar.Target – 최상위 트리거 버튼 (role="menuitem")
  • Menubar.Dropdown – 드롭다운 컨테이너, Menu.Dropdown과 같은 children을 받아요
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는 어떤 메뉴도 열려 있지 않을 때 메뉴가 어떻게 열리는지 제어해요.

  • click (기본값) – 타겟을 클릭하면 메뉴가 열려요. 메뉴가 열린 후 형제 타겟에 hover하면 즉시 전환돼요. 이는 네이티브 데스크톱 애플리케이션 메뉴 바의 동작과 일치해요.
  • hover – 모든 메뉴가 닫혀 있어도 타겟에 hover하면 바로 메뉴가 열려요.

trigger="hover"에서는 타겟에 hover하자마자 메뉴가 열리고, 포인터가 바를 떠나면 닫혀요.

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

function Demo() {
  return (
    <Menubar trigger="hover">
      <Menubar.Menu>
        <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>
  );
}

loop(기본값 true)는 화살표 키 네비게이션이 마지막 메뉴에서 첫 번째로(그 반대도) 감싸서 순환할지 제어해요.

서브메뉴 (Submenus)

Menubar.Dropdown 안에서 Menu.Sub를 사용해 중첩 서브메뉴를 만들 수 있어요. ArrowRight는 서브메뉴를 열고 첫 항목으로 포커스를 이동하며, ArrowLeft는 닫고 부모 항목으로 포커스를 돌려줘요. 자세한 내용은 Menu 서브메뉴 문서를 참고해요.

위 사용법 데모에는 File 메뉴에 서브메뉴가 포함돼 있어요.

체크박스와 라디오 항목 (Checkbox and radio items)

Menubar.Dropdown은 Menu와 같은 선택 가능 항목을 지원해요. 토글에는 Menu.CheckboxItem, 단일 선택 옵션에는 Menu.RadioGroup과 Menu.RadioItem을 지원해요. 기본적으로 체크박스나 라디오 항목을 클릭해도 메뉴는 닫히지 않아요.

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

function Demo() {
  return (
    <Menubar>
      <Menubar.Menu>
        <Menubar.Target>View</Menubar.Target>
        <Menubar.Dropdown>
          <Menu.CheckboxItem defaultChecked>Show sidebar</Menu.CheckboxItem>
          <Menu.CheckboxItem>Show status bar</Menu.CheckboxItem>
          <Menu.RadioGroup value="compact">
            <Menu.Label>Appearance</Menu.Label>
            <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>
  );
}

제어 방식 (Controlled)

openIndex와 onOpenChange로 어떤 메뉴가 열릴지 제어할 수 있어요. openIndex는 열린 Menubar.Menu의 0부터 시작하는 인덱스(DOM 순서 기준)이고, 모든 메뉴가 닫히면 null이에요.

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 onClick={() => setOpenIndex(0)}>Open File</Button>
        <Button onClick={() => setOpenIndex(1)}>Open Edit</Button>
        <Button onClick={() => setOpenIndex(null)}>Close all</Button>
      </Group>
      <Menubar openIndex={openIndex} onOpenChange={setOpenIndex}>
        <Menubar.Menu>
          <Menubar.Target>File</Menubar.Target>
          <Menubar.Dropdown>
            <Menu.Item>New file</Menu.Item>
            <Menu.Item>Save</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>
    </Stack>
  );
}

처음에 열린 메뉴로 비제어 사용을 하려면 defaultOpenIndex prop을 사용해요.

위치 (Position)

기본적으로 드롭다운은 타겟 기준 bottom-start에 위치해요. position prop을 변경하면 바의 모든 메뉴에 다른 Floating UI position을 사용할 수 있어요.

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

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

개별 메뉴는 Menu와 같은 prop을 받아요. Menubar.Menu에 전달해 단일 메뉴의 설정(예: position, withinPortal, closeOnItemClick, shadow, width, transitionProps)을 재정의할 수 있어요.

키보드 상호작용 (Keyboard interactions)

Menubar은 WAI-ARIA menubar 패턴이 정의한 키보드 상호작용을 구현해요. RTL 방향이 설정되면 ArrowLeft와 ArrowRight가 바뀌어요.

Key Description
ArrowRight 다음 최상위 메뉴로 이동, 메뉴가 열려 있으면 열린 메뉴 전환
ArrowLeft 이전 최상위 메뉴로 이동, 메뉴가 열려 있으면 열린 메뉴 전환
ArrowDown / Enter / Space 포커스된 메뉴를 열고 첫 항목으로 포커스 이동
ArrowUp 포커스된 메뉴를 열고 마지막 항목으로 포커스 이동
Escape 열린 메뉴를 닫고 타겟으로 포커스 복귀
Home / End 첫 번째 / 마지막 최상위 메뉴로 이동
Character keys 입력한 문자로 시작하는 다음 최상위 메뉴로 포커스 이동
Tab 메뉴 바 안팎으로 포커스 이동 (단일 탭 스톱)

Styles API

Menubar은 Styles API를 지원해요. classNames prop으로 컴포넌트의 내부 요소에 스타일을 추가할 수 있어요.

Styles API 셀렉터:

  • root – role="menubar"가 있는 루트 요소
  • target – Menubar.Target 최상위 트리거 버튼

접근성 (Accessibility)

Menubar은 WAI-ARIA menubar 패턴을 따릅니다.

  • 루트 요소에는 role="menubar"와 aria-orientation="horizontal"이 있어요.
  • 각 Menubar.Target은 role="menuitem", aria-haspopup="menu", aria-expanded를 가져요.
  • 메뉴 바는 단일 탭 스톱이에요. 활성 트리거만 탭 시퀀스에 포함되고, 화살표 키로 트리거 사이를 이동해요.

Menubar.Target에 전달하는 라벨은 트리거의 접근 가능한 이름으로 사용되므로, 메뉴 콘텐츠를 잘 설명하는지 확인해요.

더 알아보기 (Learn more)