Components
DropdownMenu
컨텍스트 액션 메뉴. 트리거 버튼을 누르면 옵션 리스트 표시.
Aliases (deprecated): LanguageDropdown, NoteDropdownBase, NoteOptionsDropdown, NoteShareDropdown — 모두 DropdownMenu + item schema로 이관.
Mobile
모바일에서 DropdownMenu는 Action Sheet 형태의 BottomSheet로 변환된다. 트리거 근처에 떠 있는 desktop popover 대신, 화면 하단에서 슬라이드 업 후 액션 항목을 세로 리스트로 표시한다.
- 컨테이너 anatomy(detents·grabber·swipe-to-dismiss·safe area)는 Dialog → Mobile Presentation 참조
- 액션 항목: 세로 스택, 각 행 높이 ≥44pt (터치 타겟), 좌측 prefix 아이콘 + 라벨, 우측 trailing affordance(예: checkmark, chevron) 가능
- Destructive 액션은
rose-600텍스트로 표시, 시각적 그룹 분리(separator 위) - 드래그 다운/외부 탭으로 dismiss. "취소" 버튼을 footer로 두는 iOS Action Sheet 관례를 따라도 좋음 (선택적)
- 트리거 종류: header의 "more" 버튼(
MoreHorizontal), 리스트 행의 long-press, 컨텍스트 액션 버튼
Preview
Anatomy
DropdownMenu는 trigger 클릭으로 열리는 액션 리스트. Popover와 다름 (Popover는 자유 콘텐츠, DropdownMenu는 menu items 전용).
DropdownMenuRoot
├─ DropdownMenuTrigger (외부 element — Button)
└─ DropdownMenuContent
├─ DropdownMenuLabel (optional, 그룹 헤더 — Text · caption1Medium uppercase)
├─ DropdownMenuItem (×N)
│ ├─ ItemLeading (optional, Icon · Avatar)
│ ├─ ItemLabel (Text · label3Medium)
│ └─ ItemTrailing (optional, Shortcut · Chevron · Check)
├─ DropdownMenuSeparator (optional, 그룹 구분)
├─ DropdownMenuSub (optional, 서브메뉴)
│ ├─ DropdownMenuSubTrigger (Item with Chevron)
│ └─ DropdownMenuSubContent (재귀 구조)
└─ DropdownMenuItem destructive (optional, 마지막에 배치)슬롯 규칙
| Slot | Required | Primitive | 비고 |
|---|---|---|---|
| DropdownMenuTrigger | always | (외부) | Button · IconButton — icon-only일 때 aria-label 필수 |
| DropdownMenuContent | always | — | bg-white, border-tds-stone-100, rounded-lg, shadow-md, min-w-[160px] |
| DropdownMenuLabel | optional | Text | caption1Medium uppercase, color muted-foreground |
| DropdownMenuItem | ≥1 | — | h-7 px-2 (28px), rounded-md, hover bg-stone-50 |
| ItemLeading | optional | Icon (16px) · Avatar (sm 32) | menu item 좌측 |
| ItemLabel | always | Text | label3Medium 12/18 Medium |
| ItemTrailing | optional | Text (shortcut) · Icon (Chevron / Check) | submenu = Chevron, 토글 = Check |
| DropdownMenuSeparator | optional | — | 1px solid stone-100, my-1 |
| Destructive item | optional | — | text + icon text-rose-500, hover bg-rose-50. 마지막 위치 |
Layout invariants
- Item 높이
h-7일관 (Select item과 동일한 28px) - Destructive item은 항상 list 마지막, separator로 분리 권장
- ItemLeading 있으면 다른 item에도 일관 적용 (icon column align)
- Submenu는
right방향 자동 (Radix Popper) - Esc/외부 클릭/item 선택으로 close
Variants
- context — 더보기/옵션 메뉴 (기본)
- selection — 선택 반영 (트리거 라벨에 선택값 표시 필요 시
Select고려) - checkbox — 다중 선택 (체크 표시)
- radio — 단일 선택
- submenu — 하위 메뉴 중첩
Token Mapping
| Token | Value (web) | Value (mobile — Action Sheet) | Description |
|---|---|---|---|
| presentation | popover (트리거 근처 floating) | BottomSheet (화면 하단 슬라이드 업) | 모바일은 Action Sheet 패턴 |
| bg | bg-white | bg-white | |
| border | border-tds-stone-100 | 없음 (시트가 컨테이너) | stone-100 = #E7E5E4 |
| radius | rounded-lg (8px) | top corners only r4 (16px) | |
| item height | h-7 (28px) | ≥44pt (iOS HIG) | 액션 행 높이 |
| item radius | rounded-md | 없음 (full-width row) | |
| item hover/pressed | focus:bg-tds-stone-50 | pressed:bg-stone-50 | |
| item text | text-tds-stone-600 | text-stone-700 (모바일 가독성) | |
| item typography | typography-label3Medium (12/18) | label1Medium (14/20) (모바일 가독성) | Select item과 동일 |
| item icon | 좌측 prefix 16px | 동일 (좌측 16-20px) | |
| danger item text | text-tds-rose-500 | text-rose-600 | 파괴 액션 |
| danger item hover/pressed | focus:bg-tds-rose-50 | pressed:bg-rose-50 | |
| separator | bg-tds-stone-100 | bg-stone-100, full-width | 섹션 구분 |
| elevation | Level 2 (shadow-md) | native sheet elevation | |
| z-index | z-popup (100000) | 시트 컨테이너가 처리 | |
| min-width | min-w-[160px] | 100vw (full-width) | |
| dismiss | overlay 클릭 / Esc / 항목 선택 | 드래그 다운 / 외부 탭 / 항목 선택 | iOS Action Sheet에 "취소" footer 추가 가능 |
Props
type DropdownMenuProps = {
items: Array<{
key: string;
label: string;
icon?: ReactNode;
shortcut?: string; // "⌘K" 등
disabled?: boolean;
danger?: boolean;
submenu?: DropdownMenuProps["items"];
}>;
onSelect: (key: string) => void;
trigger: ReactNode;
};Usage
<DropdownMenu
trigger={<Button type="ghost" layout="icon-only" aria-label="더보기"><MoreIcon /></Button>}
items={[
{ key: "rename", label: "이름 바꾸기", icon: <EditIcon /> },
{ key: "share", label: "공유", icon: <ShareIcon /> },
{
key: "export",
label: "내보내기",
submenu: [
{ key: "pdf", label: "PDF" },
{ key: "docx", label: "Word (.docx)" },
],
},
{ key: "delete", label: "삭제", icon: <TrashIcon />, danger: true },
]}
onSelect={handleAction}
/>A11y
role="menu"+ 내부role="menuitem"자동- Arrow key 탐색, Enter/Space 선택, Esc 닫기
- Roving focus
Do / Don't
- Do: 공간 절약이 필요한 액션 집합에 사용
- Do: 파괴적 item은 하단 섹션, separator로 분리
- Don't: 선택값이 트리거 라벨이 되어야 하면
Select사용 - Don't: item 8개 이상이면
Command또는 검색 가능한Combobox고려
