Context Menu
Context Menu
마우스 오른쪽 클릭이나 길게 누르기로 트리거되는, 포인터 위치에 나타나는 메뉴 컴포넌트예요.
출처: 문서
본문
오른쪽 클릭(또는 터치 기기의 길게 누르기)으로 열리는 컨텍스트 메뉴예요. 서브메뉴, 아이템/라벨/그룹, 체크 가능한 아이템, 라디오 아이템, 모달/비모달 모드 등을 모두 지원하며 포커스 관리와 키보드 내비게이션이 완전히 처리돼요.
Features
- 읽기 방향을 설정할 수 있는 서브메뉴 지원.
- 아이템, 라벨, 아이템 그룹 지원.
- 체크 가능한 아이템(단일/다중) + 선택적 indeterminate 상태 지원.
- 모달/비모달 모드 지원.
- side, alignment, offset, collision 처리 커스터마이즈.
- 포커스 완전 관리.
- 완전한 키보드 내비게이션.
- Typeahead 지원.
- 닫기/Dismiss 및 레이어링 동작의 높은 커스터마이즈 가능.
- 터치 기기에서 길게 누르면 트리거.
Anatomy
모든 파트를 임포트해 조립해요.
import { ContextMenu } from "radix-ui";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger />
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Label />
<ContextMenu.Item />
<ContextMenu.Group>
<ContextMenu.Item />
</ContextMenu.Group>
<ContextMenu.CheckboxItem>
<ContextMenu.ItemIndicator />
</ContextMenu.CheckboxItem>
<ContextMenu.RadioGroup>
<ContextMenu.RadioItem>
<ContextMenu.ItemIndicator />
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
<ContextMenu.Sub>
<ContextMenu.SubTrigger />
<ContextMenu.Portal>
<ContextMenu.SubContent />
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator />
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
API Reference
Menu WAI-ARIA 디자인 패턴을 준수하고, 메뉴 아이템 간 포커스 이동에 roving tabindex를 사용해요.
Root
컨텍스트 메뉴의 모든 파트를 담아요.
| Prop | Type | Default |
|---|---|---|
dir |
enum |
No default value |
open |
boolean |
No default value |
onOpenChange |
function |
No default value |
modal |
boolean |
true |
Trigger
컨텍스트 메뉴를 여는 영역이에요. 오른쪽 클릭(또는 관련 키보드 단축키)으로 메뉴를 열 대상 요소를 감싸 주세요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
disabled |
boolean |
false |
| Data attribute | Values |
|---|---|
[data-state] |
"open" | "closed" |
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 |
alignOffset |
number |
0 |
avoidCollisions |
boolean |
true |
collisionBoundary |
Boundary |
[] |
collisionPadding |
number | Padding |
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-context-menu-content-transform-origin |
The transform-origin computed from the content and arrow positions/offsets |
--radix-context-menu-content-available-width |
The remaining width between the trigger and the boundary edge |
--radix-context-menu-content-available-height |
The remaining height between the trigger and the boundary edge |
--radix-context-menu-trigger-width |
The width of the trigger |
--radix-context-menu-trigger-height |
The height of the trigger |
Arrow
서브메뉴 옆에 렌더링할 수 있는 선택적 화살표 요소예요. 트리거 아이템과 ContextMenu.Content를 시각적으로 연결해 주는 데 도움을 줘요. ContextMenu.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
여러 ContextMenu.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" | "indeterminate" |
[data-highlighted] |
Present when highlighted |
[data-disabled] |
Present when disabled |
RadioGroup
여러 ContextMenu.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" | "indeterminate" |
[data-highlighted] |
Present when highlighted |
[data-disabled] |
Present when disabled |
ItemIndicator
부모 ContextMenu.CheckboxItem 또는 ContextMenu.RadioItem이 checked일 때 렌더링돼요. 이 요소를 직접 스타일하거나 아이콘을 넣는 래퍼로 쓰거나 둘 다 할 수 있어요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
forceMount |
boolean |
No default value |
| Data attribute | Values |
|---|---|
[data-state] |
"checked" | "unchecked" | "indeterminate" |
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
서브메뉴를 여는 아이템이에요. ContextMenu.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
서브메뉴가 열렸을 때 튀어나오는 컴포넌트예요. ContextMenu.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" |
| CSS Variable | Description |
|---|---|
--radix-context-menu-content-transform-origin |
The transform-origin computed from the content and arrow positions/offsets |
--radix-context-menu-content-available-width |
The remaining width between the trigger and the boundary edge |
--radix-context-menu-content-available-height |
The remaining height between the trigger and the boundary edge |
--radix-context-menu-trigger-width |
The width of the trigger |
--radix-context-menu-trigger-height |
The height of the trigger |
Examples
서브메뉴와 함께
ContextMenu.Sub과 그 파트들을 조합해 서브메뉴를 만들 수 있어요.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Sub>
<ContextMenu.SubTrigger>Sub menu →</ContextMenu.SubTrigger>
<ContextMenu.Portal>
<ContextMenu.SubContent>
<ContextMenu.Item>Sub menu item</ContextMenu.Item>
<ContextMenu.Item>Sub menu item</ContextMenu.Item>
<ContextMenu.Arrow />
</ContextMenu.SubContent>
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
비활성 아이템과 함께
data-disabled 속성으로 비활성 아이템에 특별한 스타일을 줄 수 있어요.
// index.jsx
import { ContextMenu } from "radix-ui";
import "./styles.css";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item className="ContextMenuItem" disabled>
…
</ContextMenu.Item>
<ContextMenu.Item className="ContextMenuItem">…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
/* styles.css */
.ContextMenuItem[data-disabled] {
color: gainsboro;
}
구분선과 함께
Separator 파트로 아이템 사이에 구분선을 넣을 수 있어요.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
라벨과 함께
Label 파트로 섹션에 라벨을 붙일 수 있어요.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Label>Label</ContextMenu.Label>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
체크박스 아이템과 함께
CheckboxItem 파트로 체크할 수 있는 아이템을 추가해요.
import * as React from "react";
import { CheckIcon } from "@radix-ui/react-icons";
import { ContextMenu } from "radix-ui";
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.CheckboxItem checked={checked} onCheckedChange={setChecked}>
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Checkbox item
</ContextMenu.CheckboxItem>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
};
라디오 아이템과 함께
RadioGroup과 RadioItem 파트로 여러 개 중 하나를 체크하는 아이템을 추가해요.
import * as React from "react";
import { CheckIcon } from "@radix-ui/react-icons";
import { ContextMenu } from "radix-ui";
export default () => {
const [color, setColor] = React.useState("blue");
return (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.RadioGroup value={color} onValueChange={setColor}>
<ContextMenu.RadioItem value="red">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Red
</ContextMenu.RadioItem>
<ContextMenu.RadioItem value="blue">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Blue
</ContextMenu.RadioItem>
<ContextMenu.RadioItem value="green">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Green
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
};
복잡한 아이템과 함께
Item 파트에 이미지 같은 추가 장식 요소를 넣을 수 있어요.
import { ContextMenu } from "radix-ui";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item>
<img src="…" />
Adolfo Hess
</ContextMenu.Item>
<ContextMenu.Item>
<img src="…" />
Miyah Myles
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
콘텐츠/서브콘텐츠 크기 제한
콘텐츠(또는 서브콘텐츠)의 너비를 트리거(또는 서브트리거) 너비에 맞추고 싶을 수 있어요. 높이를 뷰포트에 넘지 않게 제한할 수도 있어요.
--radix-context-menu-trigger-width, --radix-context-menu-content-available-height 같은 여러 CSS 커스텀 프로퍼티가 이를 지원해요. 콘텐츠 크기를 제한하는 데 활용하세요.
// index.jsx
import { ContextMenu } from "radix-ui";
import "./styles.css";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
/* styles.css */
.ContextMenuContent {
width: var(--radix-context-menu-trigger-width);
max-height: var(--radix-context-menu-content-available-height);
}
Origin 인지 애니메이션
--radix-context-menu-content-transform-origin CSS 커스텀 프로퍼티를 노출해요. side, sideOffset, align, alignOffset과 충돌을 기반으로 계산된 origin에서 콘텐츠를 애니메이션하는 데 사용하세요.
// index.jsx
import { ContextMenu } from "radix-ui";
import "./styles.css";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
/* styles.css */
.ContextMenuContent {
transform-origin: var(--radix-context-menu-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 { ContextMenu } from "radix-ui";
import "./styles.css";
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
);
/* styles.css */
.ContextMenuContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.ContextMenuContent[data-side="top"] {
animation-name: slideUp;
}
.ContextMenuContent[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
메뉴 아이템 간 포커스 이동을 roving tabindex로 관리해요.
키보드 상호작용
| Key | Description |
|---|---|
Space |
Activates the focused item. |
Enter |
Activates the focused item. |
ArrowDown |
Moves focus to the next item. |
ArrowUp |
Moves focus to the previous item. |
ArrowRight``ArrowLeft |
When focus is on ContextMenu.SubTrigger, opens or closes the submenu depending on reading direction. |
Esc |
Closes the context menu |
더 알아보기 (Learn more)
- Context Menu는 Dropdown Menu와 구조가 거의 같지만 오른쪽 클릭/길게 누르기로 트리거된다는 점이 달라요.
- checkable/radio 아이템은 선택 상태를 시각화하는
ContextMenu.ItemIndicator와 함께 쓰여요.