← назад к разделу

Представьте, что вся навигация на вашем сайте — это <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-компонентов.