이 글은 누구를 위한 것인가
- 카테고리가 많아져서 메뉴 구조가 복잡해진 이커머스 팀
- 모바일에서 햄버거 메뉴 클릭율이 너무 낮은 팀
- 메가 메뉴를 처음 설계하는 UX 디자이너·개발자
들어가며
네비게이션은 "유저가 원하는 것을 빠르게 찾게 돕는" 것이 전부다. 메뉴 항목이 8개 이상이면 메가 메뉴를 고려해야 하고(메가 메뉴가 잘 작동하는 이유), 모바일에서 햄버거 메뉴는 클릭율이 낮으므로 대안을 찾아야 한다. 키보드 동작은 ARIA menubar 패턴을 기준으로 구현한다.
이 글은 bluefoxdev.kr의 이커머스 네비게이션 설계 를 참고하여 작성했습니다.
1. 네비게이션 패턴 선택 가이드
[카테고리 수별 네비게이션 패턴]
5개 이하:
패턴: 수평 탭 네비게이션
예: 홈 | 상품 | 세일 | 브랜드 | 마이페이지
6-12개:
패턴: 드롭다운 메뉴
각 카테고리에 서브메뉴
13개 이상:
패턴: 메가 메뉴 (2-3단 열 구조)
카테고리 + 서브카테고리 + 브랜드/추천
[메가 메뉴 내부 구조]
1열: 주요 카테고리 목록 (세로)
2열: 선택된 카테고리의 서브카테고리
3열 (선택): 브랜드, 추천 상품, 배너
[모바일 네비게이션 대안]
❌ 햄버거 메뉴: 클릭율 낮음, 발견 어려움
✅ 탭 바 (5개 이하): 하단 고정, 항상 보임
✅ 검색 우선: 검색창을 전면 배치
✅ 카테고리 퀵링크: 홈 화면에 카테고리 아이콘
2. 메가 메뉴 구현
"use client";
import { useState, useRef, useCallback } from "react";
interface Category {
id: string;
name: string;
slug: string;
subcategories: Subcategory[];
featuredBrands?: string[];
}
function MegaMenu({ categories }: { categories: Category[] }) {
const [activeCategory, setActiveCategory] = useState<string | null>(null);
const closeTimer = useRef<NodeJS.Timeout>();
const handleMouseEnter = useCallback((categoryId: string) => {
if (closeTimer.current) clearTimeout(closeTimer.current);
setActiveCategory(categoryId);
}, []);
const handleMouseLeave = useCallback(() => {
// 약간의 지연으로 실수로 메뉴가 닫히는 것 방지
closeTimer.current = setTimeout(() => {
setActiveCategory(null);
}, 150);
}, []);
const activeData = categories.find((c) => c.id === activeCategory);
return (
<nav className="relative" onMouseLeave={handleMouseLeave}>
{/* 카테고리 탭 */}
<ul className="flex items-center gap-1">
{categories.map((category) => (
<li key={category.id}>
<a
href={`/category/${category.slug}`}
className={`px-4 py-5 text-sm font-medium hover:text-blue-600 block ${
activeCategory === category.id ? "text-blue-600 border-b-2 border-blue-600" : "text-gray-700"
}`}
onMouseEnter={() => handleMouseEnter(category.id)}
>
{category.name}
</a>
</li>
))}
</ul>
{/* 메가 메뉴 패널 */}
{activeData && (
<div
className="absolute top-full left-0 right-0 bg-white border-t shadow-lg z-50"
onMouseEnter={() => handleMouseEnter(activeData.id)}
>
<div className="max-w-7xl mx-auto px-4 py-6 grid grid-cols-4 gap-8">
{/* 서브카테고리 그리드 */}
<div className="col-span-3 grid grid-cols-3 gap-4">
{activeData.subcategories.map((sub) => (
<div key={sub.id}>
<h3 className="font-semibold text-sm mb-2 text-gray-900">
<a href={`/category/${sub.slug}`} className="hover:text-blue-600">
{sub.name}
</a>
</h3>
<ul className="space-y-1">
{sub.children?.map((child) => (
<li key={child.id}>
<a
href={`/category/${child.slug}`}
className="text-sm text-gray-500 hover:text-blue-600"
>
{child.name}
</a>
</li>
))}
</ul>
</div>
))}
</div>
{/* 추천 배너 */}
<div className="col-span-1">
<div className="bg-gray-50 rounded-lg p-4">
<p className="text-xs text-gray-500 mb-2">이번 주 추천</p>
<a href={`/category/${activeData.slug}/sale`} className="block">
<img
src={activeData.bannerUrl}
alt="추천 배너"
className="w-full rounded-lg"
/>
</a>
</div>
</div>
</div>
</div>
)}
</nav>
);
}
마무리
메가 메뉴 설계에서 가장 중요한 것은 "마우스 딜레이 존"이다. 사용자가 대각선으로 마우스를 이동할 때 메뉴가 닫혀버리면 답답하다. 150ms 지연으로 이 문제를 해결하고, 메뉴 호버 영역을 패널까지 연결해야 한다. 모바일에서는 메가 메뉴 대신 검색과 카테고리 퀵링크로 대체하는 것이 전환율에 더 좋다.