ComponentsP3 본문

DropdownMenu

트리거 버튼 + 액션 메뉴(WAI-ARIA menu button 패턴) — 열리면 첫 항목으로 포커스 이동, 화살표 순환 탐색, Escape는 닫고 트리거로 복귀. 비제어(defaultOpen).

마지막 업데이트 2026-06-11

한눈에#

트리거 버튼 + 실행 가능한 액션 메뉴(WAI-ARIA menu button)입니다 — 열리면 첫 항목으로 포커스, 화살표 순환 탐색, 선택 시 실행 후 닫힙니다.

작업
이름 바꾸기복제삭제
  • menu button 패턴
  • 첫 항목 자동 포커스
  • 화살표 순환·섹션 헤더
  • 선택 즉시 실행·닫힘
열린 상태 재현 — 트리거 하단에 액션 메뉴가 펼쳐집니다(실제 동작은 아래에서)

트리거를 눌러 화살표 탐색·선택 실행을 직접 확인하세요:

사용 시점#

트리거로 실행 가능한 액션 목록을 펼치면 DropdownMenu — 폼 값 선택·임의 패널·명령 팔레트는 다른 컴포넌트입니다.

쓴다

이름 바꾸기·복제·삭제처럼 트리거로 펼쳐 고르면 즉시 실행되는 액션 메뉴(menu button)

대신 Select

폼에 제출할 단일 값을 고르는 선택일 때

대신 Popover

메뉴가 아닌 임의 콘텐츠 패널일 때

대신 Command

검색 가능한 명령 팔레트(⌘K)일 때

플레이그라운드#

컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.

테마 미리보기
import { DropdownMenu } from '@wds/ui-web';

<DropdownMenu
  trigger='메뉴'
  items={[
    { value: 'rename', label: '이름 변경' },
    { value: 'duplicate', label: '복제' },
    { value: 'delete', label: '삭제', danger: true },
  ]}
/>

변형#

danger · disabled 항목
  • danger — 파괴적 액션 변형, color.error-text 잉크
  • disabled — 네이티브 disabled — 포커스 탐색이 건너뜀
섹션(group) · leadingIcon · trailingText — 같은 group 연속 항목 앞에 헤더
  • group — 같은 group을 공유하는 연속 항목 앞에 비대화형 섹션 헤더(role="presentation")를 렌더 — 헤더는 menuitem이 아니라 키보드 탐색에서 건너뜁니다
  • leadingIcon — 라벨 앞 장식 아이콘(aria-hidden)
  • trailingText — 라벨 우측 보조 텍스트(키보드 숏컷 등), muted·우측 정렬

크기#

단일 크기입니다 — 트리거 높이 control.height-md(44px), 메뉴 최소 폭 11rem.

상태#

  • 열림 — 트리거 aria-expanded="true" + 메뉴 렌더, 첫 활성 항목 포커스
  • 항목 hover/focuscolor.surface-hover 배경 (트리거 열림 상태만 surface-selected)
  • Focus — 트리거 :focus-visible에 2px color.focus-ring 아웃라인

선택하면 onSelect?.(value) 호출 후 메뉴가 닫히고 트리거로 포커스가 복귀합니다.

Props#

Prop타입기본값설명
triggerReactNode트리거 콘텐츠 — 내부에서 button으로 감쌈(자체 button 전달 금지)
itemsreadonly DropdownMenuItem[]{ value, label, disabled?, danger?, group?, leadingIcon?, trailingText? } 배열 — 데이터 prop 방식
onSelect(value: string) => void항목 선택 콜백 — 호출 후 메뉴는 닫힘
openboolean제어 열림 상태 — 제공하면 제어 모드(내부 상태 무시, 모든 전환이 onOpenChange로 통지)
onOpenChange(open: boolean) => void열림/닫힘 전환 통지 — 제어/비제어 모두에서 호출
defaultOpenbooleanfalse초기 열림 여부 — 이후 상태는 내부 소유(비제어, open 미제공 시)
classNamestring루트(div)에 적용
Prop타입기본값설명
valuestring선택 시 onSelect로 전달되는 식별자
labelReactNode항목 라벨 — menuitem 접근명
disabledboolean네이티브 disabled — 선택·포커스 탐색에서 제외
dangerboolean파괴적 액션 변형 — error 잉크
groupstring섹션 그룹 — 같은 group을 공유하는 연속 항목 앞에 비대화형 헤더(role="presentation") 렌더. 헤더는 menuitem이 아니며 키보드 탐색에서 건너뜀
leadingIconReactNode라벨 앞 장식 아이콘(aria-hidden) — 접근명에 포함되지 않음
trailingTextstring라벨 우측 보조 텍스트(키보드 숏컷 등) — muted·우측 정렬·aria-hidden

접근성#

트리거가 aria-haspopup="menu" + aria-expanded + aria-controls, 팝업이 role="menu"(트리거 aria-labelledby 연결) + role="menuitem" 항목입니다. group 섹션 헤더는 role="presentation"이라 menuitem이 아니며, 화살표·Home/End 탐색에서 자연히 건너뜁니다. leadingIcon·trailingTextaria-hidden이라 항목 접근명은 label만 사용됩니다.

동작
(트리거)열고 활성 항목으로 포커스
(트리거)열고 마지막 활성 항목으로 포커스
/ (메뉴)항목 포커스 순환 이동 — 비활성 건너뜀
Home / End첫/마지막 활성 항목으로 점프
Enter / Space항목 선택 + 닫기 + 트리거 포커스 복귀
Escape선택 없이 닫기 + 트리거 포커스 복귀
Tab메뉴는 탭 순서에 끼지 않음 — 닫고 기본 이동

외부 클릭(mousedown)으로도 닫히며, 항목은 tabIndex={-1}로 화살표 탐색만 허용합니다(menu button 패턴).

토큰#

component 토큰 없이 semantic을 직접 소비합니다(신설 기준 §4 미충족).

속성토큰
트리거color.surface + color.border (hover surface-hover/border-strong · pressed surface-pressed)
트리거 높이/라운드control.height-md · radius.control
메뉴color.surface-raised · radius.md · shadow.lg · z.dropdown
항목font.size-body-2 · hover/focus color.surface-hover · pressed color.surface-pressed
dangercolor.error-text (hover 배경 color.error 톤)
비활성color.disabled-fg
모션duration.fast + ease.emphasized-decelerate(등장)/ease.standard(전환)