Реализация Mega Menu (мега-меню) на сайте
Мега-меню — навигационный компонент, раскрывающий многоколоночную панель с группировкой ссылок, изображениями, описаниями и дополнительными элементами управления. Применяется на сайтах с глубокой структурой разделов: интернет-магазины, порталы, корпоративные сайты с несколькими продуктовыми линейками. Стандартный dropdown-список становится узким местом, когда разделов больше 20 и пользователю нужен контекст вместе с навигацией.
Когда стандартное меню перестаёт справляться
Обычный выпадающий список (<ul> с position: absolute) имеет жёсткие ограничения:
- одна колонка — невозможно группировать по категориям
- нет места для вспомогательного контента (изображения, описания, промо-блоки)
- на тач-устройствах hover-логика ломается
- управление с клавиатуры либо отсутствует, либо реализовано наспех
Мега-меню решает эти проблемы за счёт другой модели раскрытия и разметки.
Архитектура компонента
Типичная структура мега-меню состоит из трёх слоёв:
Триггеры верхнего уровня — горизонтальная навигационная полоса. Каждый пункт — это либо ссылка, либо кнопка, раскрывающая панель. С точки зрения семантики: <button aria-expanded="false" aria-controls="menu-catalog">.
Панель — <div role="dialog"> или просто <div> с aria-labelledby, абсолютно или фиксированно позиционированная, занимающая всю ширину контейнера (или viewport). Внутри — CSS Grid или Flexbox с несколькими колонками.
Контент внутри панели — группы ссылок с заголовками, featured-блоки, изображения, CTA-кнопки. Структурируются через <nav> + <ul> внутри именованных секций.
<nav aria-label="Основная навигация">
<ul class="mega-nav">
<li>
<button
aria-expanded="false"
aria-controls="panel-catalog"
class="mega-nav__trigger"
>
Каталог
</button>
<div id="panel-catalog" class="mega-panel" hidden>
<div class="mega-panel__grid">
<section aria-labelledby="group-electronics">
<h3 id="group-electronics">Электроника</h3>
<ul>
<li><a href="/catalog/phones">Смартфоны</a></li>
<li><a href="/catalog/laptops">Ноутбуки</a></li>
</ul>
</section>
<!-- другие группы -->
</div>
</div>
</li>
</ul>
</nav>
Реализация на React
В React-проектах мега-меню обычно управляется через контекст или локальный state с useReducer. Анимации открытия/закрытия — через Framer Motion или CSS-трансформации.
const MegaMenu = () => {
const [activePanel, setActivePanel] = useState<string | null>(null);
const containerRef = useRef<HTMLElement>(null);
// Закрытие по клику вне меню
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setActivePanel(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// Закрытие по Escape
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setActivePanel(null);
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<nav ref={containerRef} aria-label="Основная навигация">
{navItems.map((item) => (
<MegaNavItem
key={item.id}
item={item}
isOpen={activePanel === item.id}
onToggle={() => setActivePanel(activePanel === item.id ? null : item.id)}
/>
))}
</nav>
);
};
Если меню используется в Next.js с SSR, hidden атрибут панели должен обрабатываться корректно на сервере, чтобы избежать layout shift при гидрации.
Доступность (WCAG 2.1 AA)
Это самая часто игнорируемая часть. Требования конкретные:
| Паттерн | Реализация |
|---|---|
| Открытие по Enter/Space на триггере | onKeyDown с проверкой `key === 'Enter' |
| Навигация стрелками внутри панели | roving tabindex или aria-activedescendant |
| Закрытие по Escape | глобальный обработчик или через FocusTrap |
| Фокус при закрытии | возврат на триггер через triggerRef.current?.focus() |
| Скрытие от screen reader | hidden или aria-hidden="true" на закрытой панели |
Библиотека @radix-ui/react-navigation-menu реализует большинство этих требований из коробки и является предпочтительной базой для кастомного мега-меню в React-стеке. Она использует паттерн NavigationMenu.Root + NavigationMenu.List + NavigationMenu.Item + NavigationMenu.Trigger + NavigationMenu.Content.
Мобильная версия
Мега-меню на мобильных устройствах трансформируется в аккордеон или drawer-панель. Это другой компонент, не просто адаптированный десктопный. Breakpoint-логика в CSS:
@media (max-width: 1024px) {
.mega-panel {
position: static;
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 0.3s ease;
}
.mega-panel[data-open="true"] {
grid-template-rows: 1fr;
}
}
Трюк с grid-template-rows: 0fr → 1fr позволяет анимировать высоту без фиксированного значения — это устоявшийся паттерн для аккордеонов без JavaScript-измерения высоты.
Позиционирование панели
Два основных подхода:
Full-width — панель растягивается на всю ширину страницы, привязывается к нижней границе навигационной полосы. Используется в большинстве e-commerce сайтов. Реализуется через position: fixed с top: <navbar-height> или через position: absolute на обёртке с overflow: visible.
Flyout — панель позиционируется относительно конкретного триггера. Подходит для меню с небольшим количеством групп. Требует расчёта позиции через getBoundingClientRect() для корректного поведения у края экрана (flip-логика, как в Floating UI / Popper.js).
Производительность
Панели мега-меню содержат много DOM-узлов. Если контент загружается из API (например, динамические категории каталога), важно:
- рендерить панели лениво — только после первого открытия (
mountedPanels: Set<string>) - использовать
content-visibility: autoдля скрытых секций - ограничить количество одновременно монтированных панелей
const [mounted, setMounted] = useState(false);
const handleOpen = () => {
if (!mounted) setMounted(true);
setIsOpen(true);
};
return (
<div>
<button onClick={handleOpen}>Каталог</button>
{mounted && (
<div hidden={!isOpen} className="mega-panel">
<CatalogPanelContent />
</div>
)}
</div>
);
Типовые сроки реализации
- Статическое мега-меню (фиксированные ссылки, без API) с мобильным аккордеоном — 3–5 дней
- Динамическое меню с загрузкой категорий из CMS/API + full accessibility + анимации — 7–10 дней
- Интеграция в существующий дизайн-системный компонент с unit/a11y тестами — добавить 2–3 дня







