Проблема стандартного меню при большом каталоге
Отметим: когда в каталоге больше 50 разделов, а навигация — старый <ul> с выпадающим списком, посетитель не видит подкатегории, кликает наугад, конверсия падает на 20–30%. Стандартный dropdown упирается в ограничения: одна колонка, нет места для изображений и описаний, на тач-устройствах hover-логика ломается, отсутствует клавиатурное управление. Выход — мега-меню (mega menu): панель с 4–6 колонками, изображениями и описаниями. Оно даёт контекст и ускоряет поиск товара в 2 раза.
Наша команда с 7-летним опытом в создании сложной навигации реализовала более 30 проектов с мега-меню для e-commerce площадок. Мы используем современный стек: React 18, Next.js 14, TypeScript. В статье разберём архитектуру компонента, требования доступности согласно WCAG 2.1 Success Criterion 2.4.3 Focus Order и мобильную адаптацию.
Когда обычный dropdown перестаёт работать
Стандартный выпадающий список имеет жёсткие ограничения:
- одна колонка — невозможно группировать по категориям;
- нет места для вспомогательного контента (изображения, описания, промо);
- на тач-устройствах hover-логика ломается;
- управление с клавиатуры отсутствует или реализовано наспех.
Мега-меню решает эти проблемы за счёт другой модели раскрытия и разметки. Мы гарантируем, что ваш проект получит удобную навигацию.
Архитектура компонента
Типичная структура мега-меню состоит из трёх слоёв:
Триггеры верхнего уровня — горизонтальная навигационная полоса. Каждый пункт — кнопка с aria-expanded и aria-controls. Панель — <div> с aria-labelledby, позиционированная на всю ширину. Внутри — CSS Grid или Flexbox с колонками. Контент — группы ссылок, изображения, CTA.
<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-проектах мега-меню удобно управлять через контекст и useReducer. Анимации — через Framer Motion или CSS-трансформации. Вот базовый компонент с закрытием по клику вне меню и по Escape:
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);
}, []);
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 | глобальный обработчик |
| Фокус при закрытии | возврат на триггер через triggerRef.current?.focus() |
| Скрытие от screen reader | hidden или aria-hidden="true" на закрытой панели |
Библиотека @radix-ui/react-navigation-menu реализует большинство этих требований из коробки и является предпочтительной базой в React-стеке. Она использует паттерн NavigationMenu.Root + List + Item + Trigger + Content. Наш опыт показывает, что это ускоряет разработку на 30%.
Как проверить доступность мега-меню?
Вот пошаговая инструкция для тестирования:
- Откройте меню с клавиатуры — нажмите Tab до триггера, затем Enter или Space. Убедитесь, что панель раскрывается и фокус переходит внутрь.
- Используйте стрелки вправо/влево для переключения между триггерами. Панель должна переключаться.
- Нажмите Escape — панель закроется, фокус вернётся на триггер.
- Проверьте screen reader (NVDA, VoiceOver): объявляется ли
aria-expanded,aria-controls,aria-labelledby. - Убедитесь, что закрытая панель невидима для screen reader (атрибут
hiddenилиaria-hidden="true").
Что входит в нашу работу
Передаём документацию по компоненту, настроенные доступы к репозиторию, unit-тесты для ключевых сценариев, примеры использования. Обучаем команду работе с компонентом. Оказываем поддержку 2 недели после сдачи. Вы получаете гарантию, что меню работает в трёх последних версиях Chrome, Firefox, Safari и Edge.
Мобильная версия
На мобильных устройствах мега-меню трансформируется в аккордеон. Это отдельный компонент, не адаптированный десктопный. 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-измерений в 2 раза по производительности.
Позиционирование панели
Два подхода:
- Full-width — панель по всей ширине, привязана к нижней границе меню. Часто в e-commerce.
- Flyout — у конкретного триггера. Используйте Floating UI для расчёта позиции и flip-логики.
Таблица сравнения:
| Характеристика | Full-width | Flyout |
|---|---|---|
| Занимаемая площадь | Вся ширина экрана | Только под триггером |
| Подходит для | Более 20 разделов в структуре | Менее 10 разделов |
| Сложность реализации | Средняя | Выше (расчёт позиции) |
Почему производительность важна?
Панели содержат много DOM-узлов. Если данные динамические, применяйте ленивую загрузку:
const [mounted, setMounted] = useState(false);
const handleOpen = () => {
if (!mounted) setMounted(true);
setIsOpen(true);
};
return (
<div>
<button onClick={handleOpen}>Каталог</button>
{mounted && <div hidden={!isOpen}>...</div>}
</div>
);
Технические детали анимации
Для скрытых секций используйте content-visibility: auto — это экономит ресурсы браузера. При открытии панели с динамическим контентом загружайте данные только при первом открытии (mount-on-interaction).
Сроки и стоимость
Стоимость рассчитывается индивидуально после оценки проекта. Ориентировочные сроки:
- Статическое мега-меню (фиксированные ссылки, мобильный аккордеон) — 3–5 дней.
- Динамическое меню с загрузкой из CMS/API, полной доступностью и анимациями — 7–10 дней.
- Интеграция в существующую дизайн-систему с тестами — добавить 2–3 дня.
Средняя стоимость реализации — от 200 000 до 600 000 ₽. Экономия на доработках после сдачи может составить до 100 000 ₽.
Свяжитесь с нами для точной оценки вашего проекта — мы проанализируем структуру каталога и предложим оптимальное решение. Закажите разработку мега-меню и получите консультацию инженера.







