Представьте, что вся навигация на вашем сайте — это <div onClick>. Для человека, который видит экран и держит мышь, всё работает. Но человек, который управляет компьютером с клавиатуры или слышит страницу через скринридер, попадает в тупик: кнопки не реагируют на Enter, у элементов нет имён, порядок фокуса хаотичен.
Доступность — это то, чтобы сайт работал для всех: людей с нарушениями зрения, слуха, моторики, а заодно для тех, кто пользуется только клавиатурой или включает крупный шрифт. Кроме того, доступная разметка, как правило, лучше индексируется поисковиками — браузер и поисковый робот одинаково любят правильный HTML.
Проблема: div вместо правильных элементов
Когда разработчики вешают обработчики кликов на div или span, браузер не знает, что это интерактивный элемент. Браузер не добавит его в порядок обхода Tab, не сообщит скринридеру, что элемент — кнопка, не обработает нажатие Enter.
<!-- плохо: div ничего не сообщает -->
<div onclick="save()">Сохранить</div>
<!-- хорошо: браузер уже знает всё об этой кнопке -->
<button onclick="save()">Сохранить</button>
<button> получает фокус по Tab, срабатывает по Enter и Space, а скринридер объявляет его роль — «кнопка». Всё это бесплатно, без строчки JavaScript.
Семантическая разметка
Девяносто процентов доступности — это просто правильные HTML-элементы. Браузер знает их смысл и сам выполняет нужное поведение:
<nav>— навигация: скринридер позволяет перейти сразу к ней.<main>— главное содержимое страницы.<button>— интерактивная кнопка, фокусируется и срабатывает с клавиатуры.<a href>— ссылка, открывается по Enter.<label>— подпись к полю ввода; при клике по ней фокус переходит в поле.<h1>–<h6>— заголовки в иерархии; нарушать порядок (h1 → h3, пропустив h2) не стоит.<img alt="...">— у каждой картинки должен быть атрибутalt; если картинка декоративная —alt="".
<label for="email">Электронная почта</label>
<input id="email" type="email" />
Правило простое: сначала ищи подходящий HTML-элемент. Только если его нет — добавляй ARIA.
Клавиатура и фокус
Всё, что работает мышью, должно работать с клавиатуры. Проверяется просто: отложите мышь и пройдите по странице клавишей Tab.
- Фокус переходит по Tab в логичном порядке.
- Кнопки и ссылки срабатывают по Enter.
- Кнопки (не ссылки) срабатывают и по Space.
- Видно, какой элемент сейчас в фокусе — это стиль
:focus-visible.
Последний пункт часто ломают «ради красоты»: разработчик убирает рамку outline: none, и пользователь клавиатуры перестаёт понимать, где он находится. Это один из самых распространённых и легко исправимых дефектов.
Семантические элементы дают всё это бесплатно. Кастомные виджеты — выпадающие меню, модальные окна, карусели — приходится реализовывать вручную:
- в открытом модальном окне фокус должен оставаться внутри (ловушка фокуса);
- после закрытия фокус возвращается туда, откуда окно открылось;
- нажатие Escape закрывает окно.
ARIA — только когда нет HTML-аналога
ARIA (Accessible Rich Internet Applications) — это набор атрибутов, которые добавляют смысл там, где HTML не справляется. Но есть первое правило ARIA: не используйте ARIA, если есть нативный HTML-элемент.
<button> лучше, чем <div role="button" tabindex="0"> с ручным обработчиком клавиш. Второй вариант нужно поддерживать самостоятельно и легко сделать ошибку.
Неверный ARIA хуже, чем никакого: он вводит скринридер в заблуждение и создаёт путаницу. ARIA уместен там, где у HTML нет аналога:
<!-- Уведомление, которое появляется динамически -->
<div aria-live="polite" aria-atomic="true">
Файл загружен успешно
</div>
<!-- Иконка-кнопка без видимого текста -->
<button aria-label="Закрыть">✕</button>
<!-- Аккордеон -->
<button aria-expanded="false" aria-controls="panel1">Раздел 1</button>
<div id="panel1" hidden>Содержимое</div>
aria-live — для областей, которые обновляются без перезагрузки страницы; скринридер прочитает изменение вслух. aria-label — когда у кнопки нет текста, только иконка. aria-expanded — чтобы сообщить состояние раскрытого/свёрнутого элемента.
Цвет и контраст
Информацию нельзя передавать только цветом — часть пользователей не различает оттенки. Если ошибка в форме выделена только красным, добавьте иконку или текст.
Текст должен иметь достаточный контраст с фоном. WCAG определяет минимальный порог: 4.5:1 для обычного текста и 3:1 для крупного (от 18px). Проверить контраст можно в DevTools браузера или в специальных инструментах вроде Colour Contrast Analyser.
Как проверять доступность
Автоматически. Инструмент axe (браузерное расширение или плагин для тестов) ловит типичные ошибки: отсутствующий alt, плохой контраст, неверный ARIA, нарушения иерархии заголовков. Автоматика закрывает около 30–40% проблем.
В тестах. Testing Library ищет элементы так же, как это делает скринридер — по роли и доступному имени. Если тест написан через getByRole, он заодно проверяет доступность:
// Тест не найдёт кнопку, если у неё нет понятного имени
const button = screen.getByRole("button", { name: "Сохранить" });
Если элемент не находится по роли — скорее всего, он недоступен для вспомогательных технологий.
Руками. Отложите мышь и пройдите по странице клавишами. Включите скринридер (VoiceOver на macOS, NVDA или JAWS на Windows, TalkBack на Android) и послушайте, что он читает. Это быстро выявляет проблемы, которые автоматика не замечает.
Коротко
- Правильные HTML-элементы (
<button>,<nav>,<label>) дают доступность бесплатно — не заменяйте их на<div>. - Заголовки идут по порядку, у картинок есть
alt, у полей —<label>. - Всё, что работает мышью, работает и с клавиатуры: Tab, Enter, Space, Escape.
- Видимый фокус (
:focus-visible) не убирают — без него пользователь клавиатуры теряется. - ARIA добавляют только там, где у HTML нет подходящего элемента; неверный ARIA хуже, чем никакого.
aria-live— для динамических уведомлений;aria-label— для иконок без текста;aria-expanded— для раскрывающихся элементов.- Информацию нельзя передавать только цветом; контраст текста — не менее 4.5:1.
- axe ловит ~30–40% проблем автоматически; остальное — проверка клавиатурой и скринридером.
- Testing Library с
getByRoleпроверяет доступность как побочный эффект обычных тестов.
Что почитать дальше
- Тестирование: Testing Library и Vitest — как писать тесты по ролям.
- Производительность рендеринга — оптимизация React-компонентов.