Menu Bar
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 |
Menu
최상위 메뉴 아이템으로, 트리거와 콘텐츠의 조합을 담아요.
| 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 조합으로 구성되고, 메뉴 간 이동은 좌우 화살표 키로 할 수 있어요.