Доступное раскрывающееся меню строится на семантическом элементе <nav>, обычной кнопке с состоянием aria-expanded и ссылках, доступных с клавиатуры. JavaScript должен открывать и закрывать список, не перехватывая стандартную навигацию; при закрытии клавишей Escape фокус возвращается на кнопку. Такой подход подходит для навигации сайта и вписывается в создание сайта под ключ.
Опорные решения для доступного меню
- Для раскрытия используйте кнопку, а не ссылку с фиктивным адресом.
- Связывайте кнопку и список через
aria-controls, а состояние показывайте атрибутомaria-expanded. - Сохраняйте стандартное поведение ссылок и последовательный переход фокуса клавишей Tab.
- По Escape закрывайте список и возвращайте фокус на кнопку; при клике вне меню закрывайте его без принудительного перемещения фокуса.
- Проверяйте навигацию с клавиатурой, экранным диктором и на узком экране до публикации.
Определите поведение меню до разметки
Раскрывающееся меню подходит для главной навигации, если на небольшом экране ссылки нужно убирать за кнопку или если меню должно открываться по запросу пользователя. Для короткого списка ссылок, который постоянно виден, дополнительное раскрытие обычно не нужно: оно добавляет действие и может усложнить навигацию.
Заранее решите, открывается ли меню только по нажатию или также при наведении, закрывается ли после выбора ссылки и как ведёт себя при переходе на другую страницу. Для доступной и предсказуемой реализации используйте нажатие кнопки; не делайте наведение единственным способом открыть меню.
Соберите семантическую HTML-структуру
Для реализации понадобятся HTML, CSS и JavaScript, доступ к исходникам страницы и возможность проверить результат в браузере. Если вы заказываете услуги веб-разработки, передайте исполнителю требования к клавиатурной навигации и экранным дикторам наряду с визуальным макетом. Разработка сайтов цена сама по себе не показывает, включено ли такое тестирование, поэтому уточните его отдельно.
Используйте навигационный ориентир, кнопку и обычный список ссылок. Не добавляйте ссылкам роль пункта приложения: для навигации по сайту достаточно привычных элементов HTML.
<nav class="site-nav" aria-label="Основная навигация">
<ul class="menu-list" id="primary-links" hidden>
<li><a href="/about/">О нас</a></li>
<li><a href="/services/">Услуги</a></li>
<li><a href="/contacts/">Контакты</a></li>
</ul>
</nav>
Атрибут hidden скрывает закрытый список и исключает его ссылки из последовательности фокуса. Текст кнопки должен ясно объяснять её назначение; если кнопка визуально заменена иконкой, обеспечьте ей доступное название.
Настройте раскрытие и фокус с клавиатуры
Перед началом проверьте риски и ограничения:
- Не используйте
display: noneв CSS для постоянно перекрытого списка без синхронизации с атрибутомhidden. - Не ставьте фокус на первый пункт при обычном открытии: пользователь ожидает остаться на кнопке и перейти к ссылкам клавишей Tab.
- Не закрывайте меню при нажатии Tab до того, как браузер переместит фокус на следующий элемент.
- Не перехватывайте Enter и пробел без необходимости: для кнопки эти клавиши уже поддерживаются браузером.
-
Сохраните ссылки в обычном порядке фокуса.
Не добавляйте положительные значенияtabindexи не создавайте собственную навигацию стрелками, если это не требуется отдельным интерфейсным паттерном. После открытия фокус остаётся на кнопке; следующий Tab переводит его на первую ссылку. -
Переключайте видимость и состояние.
Слушайте нажатие кнопки, меняйтеhiddenу списка и синхронно обновляйтеaria-expanded. Так визуальное состояние и информация для вспомогательных технологий не расходятся. -
Добавьте закрытие по Escape.
Если меню открыто, Escape скрывает список и возвращает фокус на кнопку. Если меню уже закрыто, обработчик не должен менять фокус. -
Закрывайте меню при выборе ссылки.
Для обычной навигационной ссылки не отменяйте переход. При смене страницы браузер сам выполняет переход; если ссылка ведёт к разделу на той же странице, не мешайте переходу к этому разделу и проверьте, что целевой контент доступен. -
Закрывайте меню при нажатии вне него.
Проверяйте, находится ли событие внутри навигационного блока. При внешнем клике скройте список, но не переводите фокус обратно на кнопку: иначе можно нарушить намерение пользователя, который нажал на другой элемент.
Пример JavaScript:
const nav = document.querySelector(".site-nav");
const toggle = nav.querySelector(".menu-toggle");
const list = nav.querySelector(".menu-list");
function setMenuOpen(open) {
toggle.setAttribute("aria-expanded", String(open));
list.hidden = !open;
}
toggle.addEventListener("click", () => {
const isOpen = toggle.getAttribute("aria-expanded") === "true";
setMenuOpen(!isOpen);
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && !list.hidden) {
setMenuOpen(false);
toggle.focus();
}
});
document.addEventListener("click", (event) => {
if (!nav.contains(event.target) && !list.hidden) {
setMenuOpen(false);
}
});
list.addEventListener("click", (event) => {
if (event.target.closest("a")) {
setMenuOpen(false);
}
});
Если на странице несколько таких блоков, каждому нужен собственный обработчик с областью действия внутри соответствующей навигации. Иначе нажатие одной кнопки может раскрыть не тот список или обработчики начнут конфликтовать.
Оформите состояния меню без потери доступности
Стилизация не должна скрывать фокус или создавать разрыв между видимым состоянием и состоянием, объявляемым вспомогательным технологиям. Проверьте результат по списку:
- У кнопки заметны состояния наведения и фокуса; индикатор фокуса не удалён без доступной замены.
- Раскрытый список визуально отличим от закрытого и не перекрывает важный контент без понятной причины.
- Контраст текста и фона позволяет различать надписи и ссылки.
- Ссылки имеют различимые названия, а не состоят только из общих слов вроде "Подробнее" без контекста.
- Атрибут
hiddenреально скрывает закрытое меню; CSS не отменяет его действие. - Увеличение текста и изменение масштаба не обрезают кнопку или пункты навигации.
- Анимация не задерживает доступ к пунктам и не мешает пользователю понять, открылось ли меню.
Пример базового CSS:
.menu-toggle:focus-visible,
.menu-list a:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
.menu-list[hidden] {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.menu-list {
scroll-behavior: auto;
}
}
Обработайте Escape, клик вне меню и переходы по ссылкам
При проверке обращайте внимание на типичные ошибки:
- Кнопка сделана элементом
<div>или ссылкой с пустым адресом - она теряет ожидаемое клавиатурное поведение. - Состояние
aria-expandedне обновляется одновременно с открытием и закрытием списка. - Закрытие по Escape не возвращает фокус на кнопку, из-за чего пользователь теряет ориентир.
- Клик вне меню закрывает его, но обработчик без проверки области срабатывает и при нажатии на саму кнопку.
- Меню закрывается при Tab до перемещения фокуса, поэтому ссылки становятся недоступны с клавиатуры.
- Переход по ссылке отменён ради закрытия меню, и навигация перестаёт работать.
- Обработчик клика срабатывает только по мыши и не учитывает клавиатурное активирование ссылки.
- Используется роль
menuдля обычной навигации без реализации соответствующего поведения клавиатуры.
Если пользователь активирует ссылку клавишей Enter, браузер запускает её стандартное действие. Не нужно вручную перемещать фокус на кнопку перед переходом: это может прервать ожидаемую навигацию.
Проверьте меню экранным диктором и на узком экране
Выберите подходящий способ проверки или сочетайте несколько:
- Клавиатурная проверка. Пройдите элементы клавишами Tab и Shift+Tab, откройте меню клавишей Enter или пробелом, закройте Escape. Фокус после Escape должен быть на кнопке.
- Проверка экранным диктором. Убедитесь, что объявляются навигационный ориентир, название кнопки, её раскрытое или свёрнутое состояние и названия ссылок.
- Проверка на узком экране. Убедитесь, что кнопка не перекрыта, ссылки доступны при увеличении масштаба, а раскрытый список не выходит за экран. Это обязательный этап для создания адаптивного сайта.
- Проверка вручную в браузере. Проверьте открытие, закрытие, переходы и клик вне меню в поддерживаемых проектом браузерах; при необходимости добавьте автоматизированные тесты как дополнительный контроль.
При планировании бюджета на создание сайта под ключ уточните, входят ли проверка доступности и исправление найденных проблем в объём работ. Если нужно заказать разработку сайта, зафиксируйте в требованиях поведение фокуса, обработку Escape и тестирование на мобильном экране.
Разбор сложных ситуаций при создании меню
Нужно ли перемещать фокус в меню при его открытии?
Для обычного раскрывающегося списка ссылок фокус обычно остаётся на кнопке, а следующий Tab переводит его на первый пункт. Это сохраняет предсказуемый порядок клавиатурной навигации.
Обязателен ли атрибут aria-controls?
Он полезен для связи кнопки с управляемым списком, как в примере. Ключевое требование - корректно обновлять aria-expanded и обеспечивать реальное открытие и закрытие списка.
Как закрывать меню по клику вне него, не ломая фокус?
Скрывайте список, если событие произошло за пределами навигационного блока. Не возвращайте фокус на кнопку при внешнем клике: пользователь может намеренно перейти к другому элементу.
Можно ли использовать роль menu для навигационных ссылок?
Для обычной навигации сайта используйте <nav>, список и ссылки без роли приложения. Роль menu предполагает иной шаблон взаимодействия с клавиатурой.
Что делать, если меню должно закрываться после перехода к якорю?
Закрывайте список при активации ссылки, не отменяя переход по адресу-якорю. Проверьте, что после перехода целевой раздел виден и доступен с клавиатуры.
Нужно ли скрывать кнопку на широком экране?
Это зависит от макета: навигация может быть постоянно раскрыта на широком экране и свёрнута на узком. Синхронизируйте CSS и состояние JavaScript при смене режима, чтобы скрытые ссылки не оставались случайно недоступными.