NavigationMenu | CoUI

NavigationMenu

호버 또는 클릭으로 콘텐츠 패널이 펼쳐지는 가로 메가-메뉴 컴포넌트

NavigationMenu#

호버 또는 클릭으로 콘텐츠 패널이 펼쳐지는 가로 메가-메뉴 컴포넌트입니다. 각 상위 항목은 콘텐츠 패널을 가지거나, 직접 링크이거나, 액션 콜백을 가질 수 있습니다.

NavigationMenu는 데이터 기반 컴포넌트입니다. CoreNavigationMenuItem 목록을 넘기면 각 항목이 가로 트리거 바에 라벨로 표시되고, content가 있는 항목은 호버/클릭 시 드롭다운 패널로 펼쳐집니다. Flutter와 Web이 동일한 파라미터 이름·동작을 갖습니다.

Live Preview#

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

  @override
  Component build(BuildContext context) {
    return NavigationMenu(
      items: [
        CoreNavigationMenuItem(
          label: 'Products',
          content: div(
            [
              Text('Web Apps').titleSmall.medium,
              Text('Build modern web experiences').bodySmall.onSurfaceVariant,
              Text('Mobile Apps').titleSmall.medium,
              Text('Native iOS and Android apps').bodySmall.onSurfaceVariant,
            ],
            styles: const Styles(
              display: Display.flex,
              flexDirection: FlexDirection.column,
              gap: Gap.all(Unit.pixels(CoreSpace.space8)),
            ),
          ),
        ),
        CoreNavigationMenuItem(
          label: 'Solutions',
          content: div(
            [
              Text('For Startups').titleSmall.medium,
              Text('Scale from zero to launch').bodySmall.onSurfaceVariant,
              Text('For Enterprise').titleSmall.medium,
              Text('Security and compliance built in').bodySmall
                  .onSurfaceVariant,
            ],
            styles: const Styles(
              display: Display.flex,
              flexDirection: FlexDirection.column,
              gap: Gap.all(Unit.pixels(CoreSpace.space8)),
            ),
          ),
        ),
        CoreNavigationMenuItem(label: 'Pricing', onTap: () {}),
        CoreNavigationMenuItem(label: 'About', onTap: () {}),
      ],
    );
  }
}
class NavigationMenuDefaultExample extends StatelessWidget {
  const NavigationMenuDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return NavigationMenu(
      items: [
        CoreNavigationMenuItem(
          label: 'Products',
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            spacing: CoreSpace.space8,
            children: [
              Text('Web Apps').titleSmall.medium,
              Text('Build modern web experiences').bodySmall.onSurfaceVariant,
              Text('Mobile Apps').titleSmall.medium,
              Text('Native iOS and Android apps').bodySmall.onSurfaceVariant,
            ],
          ),
        ),
        CoreNavigationMenuItem(
          label: 'Solutions',
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            spacing: CoreSpace.space8,
            children: [
              Text('For Startups').titleSmall.medium,
              Text('Scale from zero to launch').bodySmall.onSurfaceVariant,
              Text('For Enterprise').titleSmall.medium,
              Text('Security and compliance built in').bodySmall
                  .onSurfaceVariant,
            ],
          ),
        ),
        CoreNavigationMenuItem(label: 'Pricing', onTap: () {}),
        CoreNavigationMenuItem(label: 'About', onTap: () {}),
      ],
    );
  }
}

사용 시기 (When to Use)#

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

  • 사이트 전역 네비게이션에서 카테고리별 풍부한 콘텐츠 패널이 필요할 때
  • 마케팅 사이트의 헤더처럼 제품/솔루션을 메가-메뉴로 정리할 때
  • 호버로 미리보기를 제공하면서 클릭으로도 동작해야 할 때

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

  • Menubar: 데스크탑 앱 스타일의 파일/편집 메뉴 바에
  • DropdownMenu: 단일 버튼의 드롭다운 액션 목록에
  • Menu: 사이드바 형태의 일반 메뉴에

기본 사용법 (Basic Usage)#

NavigationMenu(
  items: [
    CoreNavigationMenuItem(
      label: 'Products',
      content: const Text('Browse the product catalog'),
    ),
    CoreNavigationMenuItem(label: 'Pricing', href: '/pricing'),
    CoreNavigationMenuItem(label: 'About', onTap: () {}),
  ],
)
NavigationMenu(
  items: [
    CoreNavigationMenuItem(
      label: 'Products',
      content: const Text('Browse the product catalog'),
    ),
    CoreNavigationMenuItem(label: 'Pricing', href: '/pricing'),
    CoreNavigationMenuItem(label: 'About', onTap: () {}),
  ],
)

Props / Parameters#

속성타입기본값설명
items List<CoreNavigationMenuItem> 필수 가로 트리거 바에 표시되는 상위 항목 목록
activeIndex int? null 활성 항목 인덱스. 지정 시 제어 모드, 생략 시 내부 상태 관리
onActiveIndexChanged void Function(int?)? null 활성 항목이 바뀔 때 호출 (null이면 닫힘)
variant CoreNavigationMenuVariant standard 시각 변형
navigationMenuStyle CoreNavigationMenuStyle? null 인스턴스별 chrome / 치수 스타일

CoreNavigationMenuItem#

속성타입기본값설명
labelString필수가로 트리거 바에 표시되는 라벨
content Widget? / Component? null 펼쳐지는 드롭다운 콘텐츠 (콘텐츠 항목)
href String? null 직접 링크 URL (링크 항목)
onTap CoreVoidCallback? null 액션 항목 활성화 시 호출

스타일 시스템 (Style System)#

NavigationMenu의 모든 chrome / 치수 오버라이드는 단일 CoreNavigationMenuStyle 슬롯으로 흐릅니다 (Epic #1415 option D). 가로 트리거 바와 드롭다운 패널 chrome을 모두 한 클래스가 받습니다.

시맨틱 vs 스타일#

  • 시맨틱 / behaviour: 위젯 파라미터로 직접 (variant, items, activeIndex)
  • chrome / dimensional: navigationMenuStyle: CoreNavigationMenuStyle? 한 곳으로

Resolve chain#

CoreNavigationMenuStyle.defaultX                       // 디자인 시스템 기본값
  → CoreNavigationMenuTheme.style                       // 프로젝트 공통
  → CoreNavigationMenuTheme.variantStyles[variant]      // variant별 공통
  → widget.navigationMenuStyle                          // 인스턴스별

CoreNavigationMenuStyle 주요 필드#

필드타입적용 영역
triggerPaddingCoreEdgeInsets?트리거 내부 패딩
triggerBorderRadiusCoreBorderRadius?트리거 모서리 반경
triggerHoverColor CoreColor? 트리거 hover / active 배경색
triggerSpacingdouble?인접 트리거 사이 간격
panelPaddingCoreEdgeInsets?드롭다운 패널 내부 패딩
panelBorderRadius CoreBorderRadius? 드롭다운 패널 모서리 반경
panelBorderWidthdouble?드롭다운 패널 보더 두께
panelBackgroundColorCoreColor?드롭다운 패널 배경색
panelBorderColorCoreColor?드롭다운 패널 보더 색
panelMinWidthdouble?드롭다운 패널 최소 너비
panelBoxShadow List<CoreShadowLayer>? 드롭다운 패널 그림자
menuOffsetdouble?트리거 바와 패널 사이 간격
durationDuration?열림 / 닫힘 색상 전환 시간

사용 예 (Flutter / Web 동일)#

NavigationMenu(
  navigationMenuStyle: const CoreNavigationMenuStyle(
    panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius16),
    panelBackgroundColor: CoreColor.token(CoreColors.surface),
  ),
  items: [
    CoreNavigationMenuItem(
      label: 'Products',
      content: const Text('Browse the product catalog'),
    ),
  ],
)

동작 스펙 (Behavior)#

인터랙션#

  • 호버: content를 가진 트리거에 호버하면 패널이 펼쳐짐
  • 클릭: content 트리거 클릭 시 토글, href 트리거는 링크 이동, onTap 트리거는 콜백 호출
  • 외부 클릭: 메뉴 외부 클릭 시 닫힘
  • Escape: 열린 패널 닫힘

상태 전환#

  • closedopen: 콘텐츠 트리거 호버 / 클릭
  • openopen (다른 항목): 다른 콘텐츠 트리거 호버 / 클릭
  • openclosed: 외부 클릭, Escape, 패널/트리거 밖으로 마우스 이탈

애니메이션#

  • 트리거 hover 배경: 150ms 색상 전환 (CoreDuration.ms150)
  • chevron 회전: 활성 시 180° (CoreDuration.ms150)

접근성 (Accessibility)#

시맨틱 역할#

  • 가로 바: role="menubar" + aria-orientation="horizontal"
  • 콘텐츠 트리거: role="menuitem" + aria-haspopup + aria-expanded
  • 링크 트리거: <a role="menuitem">
  • 드롭다운 패널: role="menu"

키보드 인터랙션#

동작
Escape열린 패널 닫기

스크린 리더#

  • Flutter: Semantics로 navigation / button / link / expanded 상태 전달
  • Web: role="menubar" / role="menuitem" / role="menu" 자동 적용

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

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

  • Menubar: 데스크탑 앱 스타일의 메뉴 바
  • Menu: 사이드바 형태의 일반 메뉴
  • Navigation: 페이지 이동을 위한 네비게이션 바