DropdownMenu | CoUI

DropdownMenu

트리거 클릭 시 하단에 액션 목록을 표시하는 드롭다운 메뉴 컴포넌트

DropdownMenu#

버튼 등 트리거 요소를 클릭하면 액션 목록을 떠 있는 패널로 표시하는 드롭다운 메뉴 컴포넌트입니다. 액션 행과 구분선, 키보드 단축키 힌트를 지원합니다.

DropdownMenu는 레거시 Flutter DropdownMenu와 Web DropdownMenu를 통일한 컴포넌트입니다. Flutter와 Web이 동일한 파라미터 이름·동작·시각을 갖습니다 — 패널은 양쪽 모두 루트 오버레이로 portal되어 ancestor overflow에 잘리지 않습니다.

Live Preview#

Web
Flutter
Loading Flutter...
class DropdownMenuDefaultExample extends StatelessComponent {
  const DropdownMenuDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return DropdownMenu(
      trigger: Button(
        variant: CoreButtonVariant.outline,
        onPressed: () {},
        child: Text('Open Menu'),
      ),
      items: [
        CoreMenuItem.action('Profile', onPressed: () {}),
        CoreMenuItem.action('Settings', onPressed: () {}),
        const CoreMenuItem.divider(),
        CoreMenuItem.action('Sign out', onPressed: () {}),
      ],
    );
  }
}
class DropdownMenuDefaultExample extends StatelessWidget {
  const DropdownMenuDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return DropdownMenu(
      trigger: Button(
        variant: CoreButtonVariant.outline,
        onPressed: () {},
        child: const Text('Open Menu'),
      ),
      items: [
        CoreMenuItem.action('Profile', onPressed: () {}),
        CoreMenuItem.action('Settings', onPressed: () {}),
        const CoreMenuItem.divider(),
        CoreMenuItem.action('Sign out', onPressed: () {}),
      ],
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • "더보기" 버튼에 편집 / 공유 / 삭제 등 액션 목록을 표시할 때
  • 특정 버튼 클릭에 반응하는 컨텍스트 액션 메뉴가 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • Select: 값을 선택하는 폼 입력에
  • ContextMenu: 우클릭으로 표시되는 상황별 메뉴에
  • Menubar: 데스크탑 앱 스타일의 상단 가로 메뉴 바에

기본 사용법 (Basic Usage)#

DropdownMenu(
  trigger: Button(
    variant: CoreButtonVariant.outline,
    onPressed: () {},
    child: const Text('Open Menu'),
  ),
  items: [
    CoreMenuItem.action('Profile', onPressed: () {}),
    CoreMenuItem.action('Settings', onPressed: () {}),
    const CoreMenuItem.divider(),
    CoreMenuItem.action('Sign out', onPressed: () {}),
  ],
)
DropdownMenu(
  trigger: Button(
    variant: CoreButtonVariant.outline,
    onPressed: () {},
    child: Text('Open Menu'),
  ),
  items: [
    CoreMenuItem.action('Profile', onPressed: () {}),
    CoreMenuItem.action('Settings', onPressed: () {}),
    const CoreMenuItem.divider(),
    CoreMenuItem.action('Sign out', onPressed: () {}),
  ],
)

Props / Parameters#

속성타입기본값설명
triggerW필수메뉴를 여는 트리거 요소
items List<CoreMenuItem<W>> 필수 드롭다운 패널의 행 — Menu / Menubar / ContextMenu 와 공유하는 통합 [CoreMenuItem] 모델
popupStyle CorePopupStyle? null 인스턴스별 패널 chrome 스타일
controller PopoverController? null 외부에서 열기/닫기를 명령형으로 제어 (Popover 와 동일한 통합 컨트롤러)

CoreMenuItem (드롭다운에서 쓰는 종류)#

드롭다운은 통합 CoreMenuItem 모델을 받는다. 주로 쓰는 두 종류:

생성자속성타입기본값설명
.action label (positional) String 필수 액션 행 텍스트
.action shortcut String? null 라벨 뒤에 표시되는 단축키 힌트
.action disabled bool false true 시 muted 처리 + onPressed 미호출
.action onPressed void Function()? null 활성 액션 선택 시 호출
.divider그룹 사이 구분선

스타일 시스템 (Style System)#

DropdownMenu의 드롭다운 패널은 떠 있는 popup 표면이므로 CorePopupStyle chrome 슬롯을 공유합니다 (Epic #1415 option D). 패널의 보더 / 반경 / 배경 / 그림자 / 패딩 / 최소 너비가 모두 popupStyle 한 곳으로 흐릅니다.

Resolve chain#

CorePopupStyle.defaultX                          // 디자인 시스템 기본값
  → CorePopupTheme.style                          // 프로젝트 공통
  → widget.popupStyle                             // 인스턴스별

엔트리 행 chrome (패딩 / hover 배경 / 구분선 색)은 CoreDropdownMenuStyledefaultEntryPadding / defaultEntryBorderRadius / defaultEntryHoverColor / defaultDividerColor / defaultEntryDuration 디자인 시스템 기본값으로 고정되어 앱 전체에서 일관성을 유지합니다.

DropdownMenu(
  popupStyle: const CorePopupStyle(
    borderRadius: CoreBorderRadius.all(CoreRadius.radius16),
  ),
  trigger: const Text('Menu'),
  items: const [CoreMenuItem.action('Item')],
)

변형 (Variants)#

단축키 힌트 / 비활성 엔트리#

DropdownMenu(
  trigger: const Text('Edit'),
  items: const [
    CoreMenuItem.action('Undo', shortcut: 'Ctrl+Z'),
    CoreMenuItem.action('Redo', disabled: true),
  ],
)

동작 스펙 (Behavior)#

인터랙션#

  • 트리거 클릭: 메뉴 열기 / 닫기 토글
  • 항목 선택: 활성 항목 클릭 시 onPressed 호출 후 닫힘
  • 외부 클릭: 메뉴 외부 클릭 시 닫힘
  • Escape: 열린 메뉴 닫힘

애니메이션#

  • 엔트리 hover 배경: 150ms 색상 전환 (CoreDuration.ms150)

사용 가이드라인 (Usage Guidelines)#

✅ Do#

관련 액션을 구분선으로 그룹화

DropdownMenu(
  trigger: const Text('More'),
  items: const [
    CoreMenuItem.action('Edit'),
    CoreMenuItem.action('Duplicate'),
    CoreMenuItem.divider(),
    CoreMenuItem.action('Delete'),
  ],
)

구분선으로 관련 액션을 묶으면 메뉴 탐색이 더 직관적이다.


❌ Don't#

폼 값 선택에 DropdownMenu 사용

// ❌ 값 선택 폼
DropdownMenu(
  trigger: const Text('Country'),
  items: const [CoreMenuItem.action('Korea')],
)

폼에서 값을 선택할 때는 Select가 레이블·유효성 검사·접근성을 더 잘 지원한다.

접근성 (Accessibility)#

시맨틱 역할#

  • 트리거: role="button" + aria-haspopup + aria-expanded (Web)
  • 드롭다운 패널: role="menu"
  • 엔트리: role="menuitem" (비활성 시 aria-disabled)
  • 구분선: role="separator"

키보드 인터랙션#

동작
Escape열린 메뉴 닫기

크로스 플랫폼 차이점 (Platform Differences)#

DropdownMenu는 Flutter와 Web이 동일한 trigger / items / popupStyle API와 동작을 갖습니다. 드롭다운 패널은 Flutter에서는 OverlayPortal, Web에서는 루트 OverlayHost의 menu 레이어로 portal되어 양쪽 모두 ancestor overflow에 잘리지 않습니다.

  • Menu: 사이드바 형태의 일반 메뉴
  • ContextMenu: 우클릭 상황별 메뉴
  • Popup: 플로팅 패널 컨테이너