Menu
동작 목록을 여는 버튼입니다. Menu.Trigger는 aria-haspopup="menu"와 aria-expanded를 가진 Button.Root입니다. Menu.Content는 CSS 앵커 포지셔닝으로 트리거에 고정된 비모달 팝오버 안의 role="menu"이며, Popover와 같은 표면입니다. 메뉴는 aria-label이나 aria-labelledby를 받지 않으면 트리거로 이름이 붙습니다.
<Menu.Root>
<Menu.Trigger variant="secondary">Actions</Menu.Trigger>
<Menu.Content>
<Menu.Item onSelect={rename}>Rename</Menu.Item>
<Menu.Separator />
<Menu.Item onSelect={remove}>Delete</Menu.Item>
</Menu.Content>
</Menu.Root>
Last action: nothing yet
키보드
트리거에서 Enter, Space, 클릭 또는 ArrowDown을 누르면 첫 항목에서 메뉴가 열리고, ArrowUp을 누르면 마지막 항목에서 열립니다. 세로 툴바 안에서는 위, 아래 키가 툴바의 컨트롤 사이를 움직입니다. 메뉴 안에서 Up과 Down은 이동하며 끝에서 처음으로 돌아가고, Home과 End는 끝으로 건너뛰고, 글자를 입력하면 그 글자로 시작하는 다음 항목으로 이동합니다. Enter, Space 또는 클릭으로 항목을 고릅니다. Escape는 메뉴를 닫고 포커스를 트리거로 돌려보냅니다. Tab은 메뉴를 닫고 포커스를 트리거 다음으로 옮깁니다(툴바 안에서는 트리거가 속한 컨트롤 묶음 다음으로). Shift+Tab은 메뉴를 닫고 트리거에 멈춥니다. 스크립트, 스크린 리더, iframe 안을 누르는 동작 등으로 포커스가 메뉴 밖 다른 곳으로 옮겨가도 메뉴가 닫힙니다. 포인터를 움직이면 그 아래 항목에 포커스가 가므로, 포인터와 키보드가 하나의 강조 표시를 함께 씁니다. 비활성 항목은 aria-disabled를 가지며, 메뉴의 관례대로 화살표 키 순서에 남아 있어 스크린 리더가 사용할 수 없는 항목으로 알립니다. 이 항목은 고를 수 없습니다. 비활성 항목에 마우스를 올리면 화살표 키처럼 그 항목에 포커스가 가므로, 강조 표시가 다른 항목에 남지 않습니다.
체크박스 항목과 라디오 항목
ARIA가 menuitem에 대해 정의한 대로, 항목의 자식은 보조 기술에 표현용으로만 전달됩니다. 따라서 항목 안에 링크, 입력, 버튼을 넣지 마세요. 그 안에서 누른 키는 그대로 두지만, 그 안을 클릭하면 항목도 선택됩니다. Menu.CheckboxItem은 aria-checked를 토글합니다. Menu.RadioGroup은 자식 Menu.RadioItem들의 값 하나를 소유합니다. 둘 다 선택한 뒤에도 열린 채로 있어 한 번에 여러 설정을 바꿀 수 있습니다. 닫으려면 closeOnSelect를 설정하세요. Menu.Group과 Menu.RadioGroup에는 aria-label이나 aria-labelledby가 필요합니다.
onOpenChange(open, reason)은 trigger, escape, outside(밖을 누르거나 밖으로 포커스가 이동), select, tab 중 하나를 알립니다. 제어 open을 갱신하지 않으면 변경이 거부됩니다. 거부된 outside에서도 누른 동작은 그대로 전달되듯이, 거부된 tab에서도 포커스는 이동합니다. 하위 메뉴는 제공하지 않습니다.
언제 쓰나요
언제: 행 동작이나 툴바 오버플로처럼, 버튼 하나 뒤에 있는 짧은 명령 목록이나 보기 설정.
쓰지 않을 때: 폼의 값 고르기 → Field.Select 또는 Combobox 레시피. 페이지 간 이동 → 링크. 자체 컨트롤이나 텍스트가 있는 콘텐츠 → role="dialog"를 쓴 Popover.
Props
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| open | boolean | 아니요 | — | 제어 열림 상태. |
| defaultOpen | boolean | 아니요 | — | 비제어일 때의 초기 열림 상태. |
| onOpenChange | (open: boolean, reason: MenuOpenChangeReason) => void | 아니요 | — | 열고 닫을 때 호출됩니다. reason은 trigger, escape, outside, select, tab 중 하나입니다. open을 바꾸지 않으면 거부로 처리됩니다. |
| children | React.ReactNode | 아니요 | — | Menu.Trigger와 Menu.Content. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| variant | ButtonVariant | 아니요 | "primary" | 버튼 variant. |
| tooltip | boolean | 아니요 | false | 버튼 팁. 메뉴 트리거에서는 기본으로 꺼져 있습니다. |
| children | React.ReactNode | 아니요 | — | 트리거 레이블. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| aria-label | string | 아니요 | — | 트리거 대신 메뉴에 이름을 붙입니다. |
| aria-labelledby | string | 아니요 | — | 메뉴의 이름이 되는 요소의 id. 기본값은 트리거입니다. |
| children | React.ReactNode | 아니요 | — | 항목, 그룹, 구분선. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| onSelect | () => void | 아니요 | — | 항목을 선택하면 실행됩니다. |
| closeOnSelect | boolean | 아니요 | true | 선택한 뒤 메뉴를 닫습니다. |
| disabled | boolean | 아니요 | — | aria-disabled. 화살표 키 순서에 남아 있어 읽히지만 선택할 수는 없습니다. |
| textValue | string | 아니요 | — | children이 일반 텍스트가 아닐 때 쓰는 타입어헤드 텍스트. |
| children | React.ReactNode | 아니요 | — | 항목 레이블. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| checked | boolean | 아니요 | — | 제어 체크 상태. |
| defaultChecked | boolean | 아니요 | — | 비제어일 때의 초기 체크 상태. |
| onCheckedChange | (checked: boolean) => void | 아니요 | — | 다음 체크 상태와 함께 호출됩니다. |
| closeOnSelect | boolean | 아니요 | false | 토글한 뒤 메뉴를 닫습니다. |
| disabled | boolean | 아니요 | — | aria-disabled. 화살표 키 순서에 남아 있어 읽히지만 선택할 수는 없습니다. |
| textValue | string | 아니요 | — | children이 일반 텍스트가 아닐 때 쓰는 타입어헤드 텍스트. |
| children | React.ReactNode | 아니요 | — | 항목 레이블. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| aria-label | string | 아니요 | — | 그룹 이름. aria-label과 aria-labelledby 중 정확히 하나만 씁니다. |
| aria-labelledby | string | 아니요 | — | 보이는 그룹 캡션의 id. |
| children | React.ReactNode | 아니요 | — | 항목. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| value | string | null | 아니요 | — | 제어 값. null이면 아무것도 선택되지 않은 제어 상태입니다. |
| defaultValue | string | null | 아니요 | — | 비제어일 때의 초기 값. |
| onValueChange | (value: string) => void | 아니요 | — | 다음 값과 함께 호출됩니다. |
| aria-label | string | 아니요 | — | 그룹 이름. aria-label과 aria-labelledby 중 정확히 하나만 씁니다. |
| aria-labelledby | string | 아니요 | — | 보이는 그룹 캡션의 id. |
| children | React.ReactNode | 아니요 | — | Menu.RadioItem 자식. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| value | string | 예 | — | 이 항목이 그룹에 설정하는 값. |
| closeOnSelect | boolean | 아니요 | false | 선택한 뒤 메뉴를 닫습니다. |
| disabled | boolean | 아니요 | — | aria-disabled. 화살표 키 순서에 남아 있어 읽히지만 선택할 수는 없습니다. |
| textValue | string | 아니요 | — | children이 일반 텍스트가 아닐 때 쓰는 타입어헤드 텍스트. |
| children | React.ReactNode | 아니요 | — | 항목 레이블. |
| className | string | 아니요 | — | 선택적 클래스. |
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| className | string | 아니요 | — | 선택적 클래스. |