Skip to content

Menu

무언가를 눌렀을 때 나타나는 액션 목록입니다. 중첩되고, 상태를 들고, 키보드만으로 끝까지 다룰 수 있습니다.

tsx
import { Button, Menu, MenuItem, MenuSeparator, MenuSubmenu } from 'neba';

<Menu trigger={<Button>액션</Button>}>
  <MenuItem shortcut="⌘E">이름 바꾸기</MenuItem>
  <MenuSubmenu label="이동">
    <MenuItem>보관함</MenuItem>
  </MenuSubmenu>
  <MenuSeparator />
  <MenuItem color="danger">삭제</MenuItem>
</Menu>;

Props

Prop타입기본값설명
triggerReactElement메뉴를 여는 요소. Base UI가 연결해 줍니다. 다른 곳에서 여는 controlled 메뉴에는 필요 없습니다
openboolean열림 여부. controlled 메뉴
defaultOpenbooleanfalse처음부터 열려 있음
onOpenChange(open: boolean) => void열리거나 닫힐 때
side공통'top' | 'right' | 'bottom' | 'left''bottom'트리거의 어느 변에 걸릴지. 자리가 없으면 반대편으로 뒤집힙니다
align공통'start' | 'center' | 'end''start'그 변 위에서의 위치
sideOffsetnumber6트리거와의 거리(px)
modalbooleantrue열려 있는 동안 뒤 페이지를 가져갈지
openOnHoverbooleanfalse호버로도 열립니다. 메뉴바처럼 열린 채로 옆 메뉴로 건너가야 할 때
loopFocusbooleantrue마지막 항목에서 화살표를 누르면 처음으로 돌아갈지
disabledbooleanfalse사용 불가. 트리거가 아무것도 열지 않습니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'높이와 타입 스케일
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통'default' | 'compact''default'행의 여백만 바꿉니다. 메뉴는 자기 팝업 폭을 스스로 정하므로 Box보다 좁은 사다리를 씁니다
childrenReactNode행들. MenuItem, MenuGroup, MenuSeparator, MenuSubmenu 등
Prop타입기본값설명
onClick(event) => void행이 하는 일
hrefstring넘기면 진짜 a가 됩니다. 링크로 이뤄진 메뉴는 링크여야 새 탭으로 열 수 있습니다
startIconReactNode라벨 앞의 내용
endIconReactNode라벨 뒤, shortcut 앞의 내용
shortcutReactNode같은 일을 하는 단축키. 행 끝에 흐리게 놓입니다 — 표시만 하고 바인딩하지는 않습니다
descriptionReactNode라벨 아래 한 줄
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'이 행만 다른 색 계열로. 지우는 행에 danger. 기본값은 메뉴의 색입니다
closeOnClickbooleantrue고르면 메뉴가 닫힐지
disabledbooleanfalse사용 불가. 목록에는 남고 타이핑 검색에도 걸립니다
labelstring타이핑 검색이 맞춰 볼 문자열. 라벨이 평범한 문자열이 아닐 때
childrenReactNode라벨
Prop타입기본값설명
labelReactNode서브메뉴를 여는 행의 라벨
startIconReactNode라벨 앞의 내용
side공통'top' | 'right' | 'bottom' | 'left''right'부모 행의 어느 쪽으로 열릴지
sideOffsetnumber4부모 메뉴와의 거리(px)
disabledbooleanfalse사용 불가
childrenReactNode중첩된 행들. 그 안에 또 MenuSubmenu를 넣을 수 있고, 깊이 제한은 없습니다

ContextMenu

Prop타입기본값설명
contentReactNode행들. Menu 안에 쓰는 것과 똑같이 씁니다
children * ReactNode오른쪽 클릭이나 길게 누르기에 반응할 영역
openboolean열림 여부
defaultOpenbooleanfalse처음부터 열려 있음
onOpenChange(open: boolean) => void열리거나 닫힐 때
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'높이와 타입 스케일
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통'default' | 'compact''default'행의 여백만 바꿉니다

예시

그룹, 링크, 아이콘

href를 주면 행이 진짜 <a>가 됩니다. 이건 사소한 차이가 아닙니다 — 링크가 아닌 링크 메뉴는 새 탭으로 열 수도, 주소를 복사할 수도 없고, 스크린 리더에게는 모든 행에 대해 틀린 말을 합니다.

중첩 메뉴

깊이 제한은 없습니다. 서브메뉴의 자식은 그냥 메뉴 행이고, 그중 하나가 또 MenuSubmenu일 수 있기 때문입니다. Base UI가 호버로 열면서 안전 삼각형을 그려 주므로, 열린 서브메뉴로 비스듬히 손을 뻗어도 닫히지 않습니다.

상태를 든 행

체크는 "그리고"를, 점은 "대신"을 뜻합니다 — 라이브러리 어디서나 Checkbox와 Radio가 만드는 것과 같은 구분입니다. 둘 다 고른 뒤에도 메뉴를 닫지 않습니다: 체크할 것들의 목록은 여러 개를 체크하는 목록이니까요.

컨텍스트 메뉴

크기

행은 데이터가 아니라 코드입니다

Selectitems 배열을 받습니다. 메뉴는 받지 않습니다. 정반대이고, 의도된 것입니다.

셀렉트의 옵션은 호출하는 쪽이 이미 갖고 있는 목록의 값들입니다. 메뉴의 행은 코드입니다 — 행마다 다른 핸들러, 다른 아이콘, 어떤 것은 서브메뉴. 데이터로 만들면 행이 취할 수 있는 모든 모양마다 변형이 하나씩 있는 items 타입이 되고, 그건 판별 유니온으로 적어 놓은 컴포넌트 트리입니다.

팝업은 Select의 팝업과 같습니다

픽셀 단위로 같습니다. 셀렉트는 무엇을 골랐는지 기억하는 메뉴이고, 서로 맞지 않는 두 개의 떠 있는 행 목록은 눈이 따로 익혀야 하는 두 개의 목록이기 때문입니다.

행의 여백만은 다릅니다. List의 행은 다른 것이 폭을 정해 준 시트를 가로지르지만, 메뉴의 행은 자기 가장 긴 라벨만큼만 넓은 팝업 안에 있습니다. md에서 Box의 px-4는 "잘라내기"라고 적힌 메뉴에 32px을 더하고, 그것이 다섯 줄짜리 메뉴가 다이얼로그만큼 넓어지는 방법입니다.

Base UI가 하는 일

메뉴를 "떠 있는 div 목록"이 아니라 메뉴로 만드는 것 전부입니다. 화살표 키의 roving focus, Home과 End, 타이핑 검색, Escape, 바깥 클릭으로 닫기, 트리거로 포커스 되돌리기, 호버로 열리는 서브메뉴와 안전 삼각형, 그리고 그 모든 것이 스크린 리더에게 의미를 갖게 하는 menu / menuitem 역할.

여기 있는 것은 표면과 사다리와 행의 배치입니다.

접근성

  • 지우는 행에는 color="danger"를 주세요. 색 계열 전체가 넘어가므로 글자색, 옅은 배경, 포커스 링이 함께 바뀝니다.
  • 라벨이 평범한 문자열이 아니면 label로 타이핑 검색이 맞춰 볼 문자열을 주세요.
  • disabled 행은 목록에 남고 타이핑 검색에도 걸립니다. 사라지지 않는 것이 요점입니다 — 없어진 행은 "여기서는 쓸 수 없음"이 아니라 "그런 건 없음"을 뜻하니까요.

Released under the MIT License