Розробка мега-меню: реалізація, адаптація, доступність (React, WCAG)
Проблема стандартного меню при великому каталозі
Відзначимо: коли в каталозі більше 50 розділів, а навігація — старий <ul> з випадаючим списком, відвідувач не бачить підкатегорії, клікає навмання, конверсія падає на 20–30%. Стандартний dropdown впирається в обмеження: одна колонка, немає місця для зображень та описів, на тач-пристроях hover-логіка ламається, відсутнє клавіатурне керування. Вихід — мега-меню (mega menu): панель з 4–6 колонками, зображеннями та описами. Воно дає контекст і пришвидшує пошук товару в 2 рази.
Наша команда з 10+ років досвіду у створенні складної навігації реалізувала понад 40 проектів з мега-меню для 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 дні.
Зв'яжіться з нами для точної оцінки вашого проекту — ми проаналізуємо структуру каталогу та запропонуємо оптимальне рішення. Замовте розробку мега-меню та отримайте консультацію інженера.







