Menu Bar

데스크톱 애플리케이션에서 흔히 볼 수 있는, 일관된 명령 모음에 빠르게 접근할 수 있게 해 주는 시각적으로 지속되는 메뉴예요.

출처: 문서

본문

데스크톱 앱의 상단 메뉴바(FIle/Edit/View 등)처럼 항상 보이는 메뉴 컴포넌트예요. 여러 개의 Menubar.Menu로 구성되고, 각 메뉴는 트리거와 드롭다운 콘텐츠 조합으로 이뤄져요.

Features

  • 제어(controlled)/비제어(uncontrolled) 방식 모두 지원.
  • 읽기 방향을 설정할 수 있는 서브메뉴 지원.
  • 아이템, 라벨, 아이템 그룹 지원.
  • 체크 가능한 아이템(단일/다중) 지원.
  • side, alignment, offset, collision 처리 커스터마이즈.
  • 선택적으로 가리키는 화살표(arrow) 렌더링.
  • 포커스 완전 관리.
  • 완전한 키보드 내비게이션.
  • Typeahead 지원.

Anatomy

모든 파트를 임포트해 조립해요.

import { Menubar } from "radix-ui";

export default () => (
  <Menubar.Root>
    <Menubar.Menu>
      <Menubar.Trigger />

      <Menubar.Portal>
        <Menubar.Content>
          <Menubar.Label />

          <Menubar.Item />

          <Menubar.Group>
            <Menubar.Item />
          </Menubar.Group>

          <Menubar.CheckboxItem>
            <Menubar.ItemIndicator />
          </Menubar.CheckboxItem>

          <Menubar.RadioGroup>
            <Menubar.RadioItem>
              <Menubar.ItemIndicator />
            </Menubar.RadioItem>
          </Menubar.RadioGroup>

          <Menubar.Sub>
            <Menubar.SubTrigger />

            <Menubar.Portal>
              <Menubar.SubContent />
            </Menubar.Portal>
          </Menubar.Sub>

          <Menubar.Separator />

          <Menubar.Arrow />
        </Menubar.Content>
      </Menubar.Portal>
    </Menubar.Menu>
  </Menubar.Root>
);

API Reference

Root

메뉴바의 모든 파트를 담아요.

Prop Type Default
asChild boolean false
defaultValue string No default value
value string No default value
onValueChange function No default value
dir enum No default value
loop boolean false

최상위 메뉴 아이템으로, 트리거와 콘텐츠의 조합을 담아요.

Prop Type Default
asChild boolean false
value string No default value

Trigger

콘텐츠를 토글하는 버튼이에요. 기본적으로 Menubar.Content는 트리거에 맞춰 위치하게 돼요.

Prop Type Default
asChild boolean false
Data attribute Values
[data-state] "open" | "closed"
[data-highlighted] Present when highlighted
[data-disabled] Present when disabled

Portal

사용하면 content 파트를 body로 포털해요.

Prop Type Default
forceMount boolean No default value
container HTMLElement document.body

Content

메뉴가 열렸을 때 튀어나오는 컴포넌트예요.

Prop Type Default
asChild boolean false
loop boolean false
onCloseAutoFocus function No default value
onEscapeKeyDown function No default value
onPointerDownOutside function No default value
onFocusOutside function No default value
onInteractOutside function No default value
forceMount boolean No default value
side enum "bottom"
sideOffset number 0
align enum "center"
alignOffset number 0
avoidCollisions boolean true
collisionBoundary Boundary []
collisionPadding number | Padding 0
arrowPadding number 0
sticky enum "partial"
hideWhenDetached boolean false
Data attribute Values
[data-state] "open" | "closed"
[data-side] "left" | "right" | "bottom" | "top"
[data-align] "start" | "end" | "center"
CSS Variable Description
--radix-menubar-content-transform-origin The transform-origin computed from the content and arrow positions/offsets
--radix-menubar-content-available-width The remaining width between the trigger and the boundary edge
--radix-menubar-content-available-height The remaining height between the trigger and the boundary edge
--radix-menubar-trigger-width The width of the trigger
--radix-menubar-trigger-height The height of the trigger

Arrow

메뉴바 메뉴 옆에 렌더링할 수 있는 선택적 화살표 요소예요. 트리거와 Menubar.Content를 시각적으로 연결해 주는 데 도움을 줘요. Menubar.Content 안에 렌더링해야 해요.

Prop Type Default
asChild boolean false
width number 10
height number 5

Item

메뉴바 아이템을 담는 컴포넌트예요.

Prop Type Default
asChild boolean false
disabled boolean No default value
onSelect function No default value
textValue string No default value
Data attribute Values
[data-highlighted] Present when highlighted
[data-disabled] Present when disabled

Group

여러 Menubar.Item을 그룹화하는 데 사용해요.

Prop Type Default
asChild boolean false

Label

라벨을 렌더링하는 데 사용해요. 화살표 키로 포커스되지 않아요.

Prop Type Default
asChild boolean false

CheckboxItem

체크박스처럼 제어되고 렌더링되는 아이템이에요.

Prop Type Default
asChild boolean false
checked boolean | 'indeterminate' No default value
onCheckedChange function No default value
disabled boolean No default value
onSelect function No default value
textValue string No default value
Data attribute Values
[data-state] "checked" | "unchecked"
[data-highlighted] Present when highlighted
[data-disabled] Present when disabled

RadioGroup

여러 Menubar.RadioItem을 그룹화하는 데 사용해요.

Prop Type Default
asChild boolean false
value string No default value
onValueChange function No default value

RadioItem

라디오처럼 제어되고 렌더링되는 아이템이에요.

Prop Type Default
asChild boolean false
value* string No default value
disabled boolean No default value
onSelect function No default value
textValue string No default value
Data attribute Values
[data-state] "checked" | "unchecked"
[data-highlighted] Present when highlighted
[data-disabled] Present when disabled

ItemIndicator

부모 Menubar.CheckboxItem 또는 Menubar.RadioItem이 checked일 때 렌더링돼요. 이 요소를 직접 스타일하거나 아이콘을 넣는 래퍼로 쓰거나 둘 다 할 수 있어요.

Prop Type Default
asChild boolean false
forceMount boolean No default value
Data attribute Values
[data-state] "checked" | "unchecked"

Separator

메뉴바 메뉴에서 아이템을 시각적으로 분리하는 데 사용해요.

Prop Type Default
asChild boolean false

Sub

서브메뉴의 모든 파트를 담아요.

Prop Type Default
defaultOpen boolean No default value
open boolean No default value
onOpenChange function No default value

SubTrigger

서브메뉴를 여는 아이템이에요. Menubar.Sub 안에 렌더링해야 해요.

Prop Type Default
asChild boolean false
disabled boolean No default value
textValue string No default value
Data attribute Values
[data-state] "open" | "closed"
[data-highlighted] Present when highlighted
[data-disabled] Present when disabled

SubContent

서브메뉴가 열렸을 때 튀어나오는 컴포넌트예요. Menubar.Sub 안에 렌더링해야 해요.

Prop Type Default
asChild boolean false
loop boolean false
onEscapeKeyDown function No default value
onPointerDownOutside function No default value
onFocusOutside function No default value
onInteractOutside function No default value
forceMount boolean No default value
sideOffset number 0
align enum "start"
alignOffset number 0
avoidCollisions boolean true
collisionBoundary Boundary []
collisionPadding number | Padding 0
arrowPadding number 0
sticky enum "partial"
hideWhenDetached boolean false
Data attribute Values
[data-state] "open" | "closed"
[data-side] "left" | "right" | "bottom" | "top"
[data-align] "start" | "end" | "center"
[data-orientation] "vertical" | "horizontal"
CSS Variable Description
--radix-menubar-content-transform-origin The transform-origin computed from the content and arrow positions/offsets
--radix-menubar-content-available-width The remaining width between the trigger and the boundary edge
--radix-menubar-content-available-height The remaining height between the trigger and the boundary edge
--radix-menubar-trigger-width The width of the trigger
--radix-menubar-trigger-height The height of the trigger

Examples

서브메뉴와 함께

Menubar.Sub과 그 파트들을 조합해 서브메뉴를 만들 수 있어요.

<Menubar.Root>
  <Menubar.Menu>
    <Menubar.Trigger>…</Menubar.Trigger>

    <Menubar.Portal>
      <Menubar.Content>
        <Menubar.Item>…</Menubar.Item>

        <Menubar.Item>…</Menubar.Item>

        <Menubar.Separator />

        <Menubar.Sub>
          <Menubar.SubTrigger>Sub menu →</Menubar.SubTrigger>

          <Menubar.Portal>
            <Menubar.SubContent>
              <Menubar.Item>Sub menu item</Menubar.Item>

              <Menubar.Item>Sub menu item</Menubar.Item>

              <Menubar.Arrow />
            </Menubar.SubContent>
          </Menubar.Portal>
        </Menubar.Sub>

        <Menubar.Separator />

        <Menubar.Item>…</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>

비활성 아이템과 함께

data-disabled 속성으로 비활성 아이템에 특별한 스타일을 줄 수 있어요.

// index.jsx

import { Menubar } from "radix-ui";

import "./styles.css";

export default () => (
  <Menubar.Root>
    <Menubar.Menu>
      <Menubar.Trigger>…</Menubar.Trigger>

      <Menubar.Portal>
        <Menubar.Content>
          <Menubar.Item className="MenubarItem" disabled>
            …
          </Menubar.Item>

          <Menubar.Item className="MenubarItem">…</Menubar.Item>
        </Menubar.Content>
      </Menubar.Portal>
    </Menubar.Menu>
  </Menubar.Root>
);
/* styles.css */

.MenubarItem[data-disabled] {
  color: gainsboro;
}

구분선과 함께

Separator 파트로 아이템 사이에 구분선을 넣을 수 있어요.

<Menubar.Root>
  <Menubar.Menu>
    <Menubar.Trigger>…</Menubar.Trigger>

    <Menubar.Portal>
      <Menubar.Content>
        <Menubar.Item>…</Menubar.Item>

        <Menubar.Separator />

        <Menubar.Item>…</Menubar.Item>

        <Menubar.Separator />

        <Menubar.Item>…</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>

라벨과 함께

Label 파트로 섹션에 라벨을 붙일 수 있어요.

<Menubar.Root>
  <Menubar.Menu>
    <Menubar.Trigger>…</Menubar.Trigger>

    <Menubar.Portal>
      <Menubar.Content>
        <Menubar.Label>Label</Menubar.Label>

        <Menubar.Item>…</Menubar.Item>

        <Menubar.Item>…</Menubar.Item>

        <Menubar.Item>…</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>

체크박스 아이템과 함께

CheckboxItem 파트로 체크할 수 있는 아이템을 추가해요.

import * as React from "react";

import { CheckIcon } from "@radix-ui/react-icons";

import { Menubar } from "radix-ui";

export default () => {
  const [checked, setChecked] = React.useState(true);

  return (
    <Menubar.Root>
      <Menubar.Menu>
        <Menubar.Trigger>…</Menubar.Trigger>

        <Menubar.Portal>
          <Menubar.Content>
            <Menubar.Item>…</Menubar.Item>

            <Menubar.Item>…</Menubar.Item>

            <Menubar.Separator />

            <Menubar.CheckboxItem checked={checked} onCheckedChange={setChecked}>
              <Menubar.ItemIndicator>
                <CheckIcon />
              </Menubar.ItemIndicator>
              Checkbox item
            </Menubar.CheckboxItem>
          </Menubar.Content>
        </Menubar.Portal>
      </Menubar.Menu>
    </Menubar.Root>
  );
};

라디오 아이템과 함께

RadioGroup과 RadioItem 파트로 여러 개 중 하나를 체크하는 아이템을 추가해요.

import * as React from "react";

import { CheckIcon } from "@radix-ui/react-icons";

import { Menubar } from "radix-ui";

export default () => {
  const [color, setColor] = React.useState("blue");

  return (
    <Menubar.Root>
      <Menubar.Menu>
        <Menubar.Trigger>…</Menubar.Trigger>

        <Menubar.Portal>
          <Menubar.Content>
            <Menubar.RadioGroup value={color} onValueChange={setColor}>
              <Menubar.RadioItem value="red">
                <Menubar.ItemIndicator>
                  <CheckIcon />
                </Menubar.ItemIndicator>
                Red
              </Menubar.RadioItem>

              <Menubar.RadioItem value="blue">
                <Menubar.ItemIndicator>
                  <CheckIcon />
                </Menubar.ItemIndicator>
                Blue
              </Menubar.RadioItem>
            </Menubar.RadioGroup>
          </Menubar.Content>
        </Menubar.Portal>
      </Menubar.Menu>
    </Menubar.Root>
  );
};

복잡한 아이템과 함께

Item 파트에 이미지 같은 추가 장식 요소를 넣을 수 있어요.

import { Menubar } from "radix-ui";

export default () => (
  <Menubar.Root>
    <Menubar.Menu>
      <Menubar.Trigger>…</Menubar.Trigger>

      <Menubar.Portal>
        <Menubar.Content>
          <Menubar.Item>
            <img src="…" />
            Adolfo Hess
          </Menubar.Item>

          <Menubar.Item>
            <img src="…" />
            Miyah Myles
          </Menubar.Item>
        </Menubar.Content>
      </Menubar.Portal>
    </Menubar.Menu>
  </Menubar.Root>
);

콘텐츠/서브콘텐츠 크기 제한

콘텐츠(또는 서브콘텐츠)의 너비를 트리거(또는 서브트리거) 너비에 맞추고 싶을 수 있어요. 높이를 뷰포트에 넘지 않게 제한할 수도 있어요.

--radix-menubar-trigger-width, --radix-menubar-content-available-height 같은 여러 CSS 커스텀 프로퍼티가 이를 지원해요. 콘텐츠 크기를 제한하는 데 활용하세요.

// index.jsx

import { Menubar } from "radix-ui";

import "./styles.css";

export default () => (
  <Menubar.Root>
    <Menubar.Trigger>…</Menubar.Trigger>

    <Menubar.Portal>
      <Menubar.Content className="MenubarContent" sideOffset={5}>
        …
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Root>
);
/* styles.css */

.MenubarContent {
  width: var(--radix-menubar-trigger-width);

  max-height: var(--radix-menubar-content-available-height);
}

Origin 인지 애니메이션

--radix-menubar-content-transform-origin CSS 커스텀 프로퍼티를 노출해요. side, sideOffset, align, alignOffset과 충돌을 기반으로 계산된 origin에서 콘텐츠를 애니메이션하는 데 사용하세요.

// index.jsx

import { Menubar } from "radix-ui";

import "./styles.css";

export default () => (
  <Menubar.Root>
    <Menubar.Menu>
      <Menubar.Trigger>…</Menubar.Trigger>

      <Menubar.Portal>
        <Menubar.Content className="MenubarContent">…</Menubar.Content>
      </Menubar.Portal>
    </Menubar.Menu>
  </Menubar.Root>
);
/* styles.css */

.MenubarContent {
  transform-origin: var(--radix-menubar-content-transform-origin);

  animation: scaleIn 0.5s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

Collision 인지 애니메이션

data-side와 data-align 속성을 노출해요. 이 값들은 충돌을 반영해 런타임에 바뀌어요. 충돌과 방향에 인지된 애니메이션을 만드는 데 사용하세요.

// index.jsx

import { Menubar } from "radix-ui";

import "./styles.css";

export default () => (
  <Menubar.Root>
    <Menubar.Menu>
      <Menubar.Trigger>…</Menubar.Trigger>

      <Menubar.Portal>
        <Menubar.Content className="MenubarContent">…</Menubar.Content>
      </Menubar.Portal>
    </Menubar.Menu>
  </Menubar.Root>
);
/* styles.css */

.MenubarContent {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

.MenubarContent[data-side="top"] {
  animation-name: slideUp;
}

.MenubarContent[data-side="bottom"] {
  animation-name: slideDown;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Accessibility

Menu Button WAI-ARIA 디자인 패턴을 준수하고, 메뉴 아이템 간 포커스 이동을 roving tabindex로 관리해요.

키보드 상호작용

Key Description
Space When focus is on Menubar.Trigger, opens the menubar and focuses the first item. When focus is on an item, activates the focused item.
Enter When focus is on Menubar.Trigger, opens the associated menu. When focus is on an item, activates the focused item.
ArrowDown When focus is on Menubar.Trigger, opens the associated menu. When focus is on an item, moves focus to the next item.
ArrowUp When focus is on an item, moves focus to the previous item.
ArrowRight``ArrowLeft When focus is on a Menubar.Trigger, moves focus to the next or previous item. When focus is on a Menubar.SubTrigger, opens or closes the submenu depending on reading direction. When focus is within a Menubar.Content, opens the next menu in the menubar.
Esc Closes the currently open menu and moves focus to its Menubar.Trigger.

더 알아보기 (Learn more)

  • Menubar는 데스크톱 앱의 상단 메뉴바 스타일을 웹에서 구현할 때 쓰는 컴포넌트예요.
  • 각 메뉴는 Menubar.Menu 안에 Trigger + Content 조합으로 구성되고, 메뉴 간 이동은 좌우 화살표 키로 할 수 있어요.