Есть очень простой способ проверить доступность сайта.
Открываешь страницу. Видишь кнопки. Нажимаешь мышкой. Всё работает.
Значит, доступность есть. Можно закрывать задачу. Особенно если дизайнер не жалуется, заказчик ничего не спрашивал, а Lighthouse пока никто не запускал.
Правда, пользователь почему-то не обязан пользоваться сайтом так же, как разработчик. У него может не быть мыши. Он может плохо видеть текст, увеличить интерфейс до 200%, пользоваться скринридером или открыть телефон на солнце. Иногда это совершенно обычный человек, который просто нажал Tab.
И внезапно обнаружил, что наш прекрасный интерфейс перестал существовать.
<div> подходит для всего
HTML давно достиг совершенства. Практически любой интерфейс можно построить из одного элемента.
Нужна кнопка?
<div class="button">
Сохранить
</div>
Нужна ссылка или переключатель? Ещё пара div, немного CSS и обработчик click. Визуально всё неотличимо от настоящих элементов. Получается удобно. Для разработчика.
Человек видит «Сохранить», «Профиль» и «Получать уведомления». Generic-контейнеры часто вообще не добавляют полезной структуры в accessibility tree. Пользователь может услышать текст без роли, состояния и ожидаемого поведения — и получить предложение самостоятельно угадать замысел автора.
Здесь проявляется недооценённое свойство веб-платформы: семантика HTML — это тоже API. Только пользуются им браузеры, скринридеры, поисковые системы и инструменты автоматизации.
Семантика — не любовь к красивому коду
<nav>, <main>, <article> и <footer> выглядят аккуратнее стены из div, но дело не в эстетике.
Семантика сообщает, что находится на странице. <nav> обозначает навигацию, <button> — действие, <a> — переход, <h2> — уровень заголовка, а <label> связывает описание с полем.
Это кажется мелочью, пока интерфейс воспринимается глазами. Уберите CSS, мышь и возможность понять по синей заливке, что надпись кликабельна. Окажется, что HTML несёт гораздо больше информации, чем мы привыкли замечать.
А потом появилась ARIA
Если стандартный HTML кажется слишком простым, разработчик наконец может проявить себя:
<div
role="button"
tabindex="0"
aria-label="Сохранить изменения"
>
Сохранить
</div>
Количество атрибутов внушает доверие. Но ARIA не превращает плохую разметку в хорошую автоматически. Если нативный элемент уже решает задачу, почти всегда лучше начать с него. Вместо того чтобы объяснять браузеру «поверь, этот div — кнопка», можно написать кнопку.
ARIA полезна там, где HTML-семантики недостаточно. Например, для состояния раскрытия:
<button
aria-expanded="false"
aria-controls="filters"
>
Фильтры
</button>
После открытия значение меняется на true. А сообщение «Файл загружен» можно поместить в область с aria-live="polite", чтобы скринридер сообщил об обновлении без резкого прерывания пользователя.
ARIA дополняет семантику, а не служит коробкой пластырей поверх кликабельных div.
Keyboard navigation? Есть же мышка
Один из самых полезных тестов интерфейса занимает минуту. Убираем руку с мыши и пытаемся пройти основной сценарий клавиатурой.
Мне нравится этот тест за беспощадную простоту: знакомая страница за несколько нажатий перестаёт быть картинкой и показывает, есть ли под ней настоящий интерфейс.
Tab и Shift + Tab перемещают фокус. Enter и Space активируют подходящие элементы. Escape закрывает меню или диалог, когда это ожидаемо. Стрелки работают внутри компонентов, для которых такой паттерн предусмотрен.
Попробуйте открыть меню, перейти в кабинет, заполнить форму, выбрать значение, вызвать модальное окно, закрыть его и отправить данные.
Очень быстро начинаются чудеса. Фокус пропадает. В список нельзя попасть. Диалог открылся, а клавиатура продолжает гулять по странице под ним. После закрытия непонятно, куда вернулся пользователь.
А иногда невозможно даже увидеть текущий элемент, потому что когда-то в CSS появилось:
*:focus {
outline: none;
}
Выглядит аккуратно. Особенно если не пользоваться клавиатурой.
Уберите outline. Пользователь догадается
Стандартный outline действительно не всегда вписывается в дизайн. Проблема возникает не тогда, когда его заменили, а когда ничего не дали взамен.
Для человека, который перемещается клавиатурой, фокус — это указатель мыши. Представьте интерфейс с невидимым курсором.
button:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}
Оформление может быть любым, если оно заметно на светлой и тёмной теме. Пользователь должен понимать, где находится.
Серый текст на сером фоне выглядит современно
Возьмём #999 на #eee, шрифт 12 пикселей и начертание 300. Получится спокойный интерфейс. Главное — не пытаться его читать.
Плохой контраст мешает не только людям с нарушениями зрения. Его замечает человек с тусклым монитором, телефоном на солнце, возрастными изменениями зрения или просто уставший после рабочего дня.
Для обычного текста WCAG AA ориентируется на контраст не ниже 4.5:1, для крупного — 3:1. У значимых границ элементов и индикатора фокуса тоже должен быть различимый контраст. Проверять это надёжнее инструментом, а не взглядом автора макета на хорошем дисплее.
И здесь accessibility неожиданно перестаёт выглядеть специализированной функцией. Читаемый текст — обычный UX.
Placeholder прекрасно заменяет label
Форму регистрации можно сделать минималистичной:
<input placeholder="Email">
<input placeholder="Пароль">
Красиво — пока пользователь не начал вводить. После этого подсказка исчезла, а ему остаётся вспоминать назначение каждого поля.
Скучный вариант работает надёжнее:
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
>
Браузер понимает связь, скринридер объявляет название поля, а на подпись можно нажать мышкой, чтобы перевести фокус в input.
Ошибку можно просто покрасить красным
Пользователь отправляет форму, одно поле становится красным — всё понятно. Особенно тому, кто не различает этот цвет.
Ошибка должна существовать не только внутри CSS. Нужен текст, а поле следует связать с ним:
<label for="email">Email</label>
<input
id="email"
aria-describedby="email-error"
aria-invalid="true"
>
<p id="email-error">
Введите корректный email
</p>
aria-describedby поможет, когда фокус попадёт в поле, но сам по себе не гарантирует объявления новой ошибки. После отправки можно перевести фокус на сводку ошибок или первое невалидное поле; для подходящего сценария — использовать заранее подготовленную live-область.
Фокус должен оставаться предсказуемым, а пользователь — получить понятное сообщение о том, что исправить. Красная рамка может остаться, но больше не несёт весь смысл в одиночку.
Accessibility начинается раньше ARIA
Есть соблазн сначала сделать сайт, написать JavaScript, всё проверить, а в конце добавить слой aria-*.
Но большая часть проблем возникает раньше. Нативные <button>, <a>, <nav>, <main>, <label>, <fieldset> и <legend>; логичный порядок DOM; видимый фокус; читаемый контраст; интерфейс, который не зависит только от цвета.
Если эти решения принимаются сразу, отдельной магической стадии «теперь сделаем сайт доступным» может не понадобиться. Accessibility становится свойством интерфейса, а не ремонтом после релиза.
Самый дешёвый аудит
Для первого прохода не нужен тяжёлый набор инструментов. Откройте сайт и не пользуйтесь мышью. Пройдите основные сценарии клавишей Tab. Увеличьте страницу до 200%. Проверьте, сохраняется ли смысл без цвета. Отправьте форму с ошибками. Посмотрите структуру заголовков и порядок фокуса в диалоге.
Проверьте, действительно ли кнопки являются кнопками, а ссылки — ссылками. Потом запустите автоматический аудит: он быстро найдёт часть проблем с именами, контрастом и структурой.
И хотя бы один раз откройте знакомую страницу со скринридером. Интерфейс внезапно начинает выглядеть совершенно иначе. Точнее — звучать.
Автоматический инструмент не докажет доступность целиком, как зелёные тесты не доказывают отсутствие багов. Но вместе с клавиатурным проходом и ручной проверкой он отлично показывает, с чего начать.
Немного неудобная правда
Доступность откладывают по той же причине, что тесты, мониторинг и нормальную обработку ошибок. Пока всё работает у разработчика, проблема кажется теоретической.
Фраза «нашей аудитории это не нужно» тоже держится на догадке. Мы не знаем, кто завтра откроет страницу с травмированной рукой, телефоном на солнце, увеличенным системным шрифтом или привычкой проходить формы клавиатурой.
Но разработчик сидит перед хорошим монитором, пользуется привычным браузером, знает интерфейс и заранее понимает, что случится после нажатия. Почти всегда у него есть мышь или трекпад.
Получается идеальный пользователь. И, пожалуй, самый нерепрезентативный из возможных.
Хорошая доступность обычно незаметна. Кнопка работает, форма понятна, фокус оказывается там, где его ждут, текст читается, диалог закрывается, ошибки не требуют знания дизайнерского языка. Никто не пишет благодарность за <button> вместо <div>. Но именно такие решения и образуют хороший интерфейс.
Поэтому доступность необязательна
Используйте div вместо кнопок. Убирайте outline. Делайте серый текст на сером фоне. Заменяйте label на placeholder. Показывайте ошибки исключительно цветом.
И считайте, что если интерфейс прекрасно работает мышкой на вашем MacBook, задача выполнена.
А если хочется поступить немного менее уверенно, начните с неожиданно сложного требования: сделать так, чтобы сайтом могли пользоваться не только его разработчики.
Доступность — не про «особенных» пользователей. Она начинается с более честного предположения: все пользователи разные, и никому из них не нужно доказывать нам, что его способ пользоваться сайтом достаточно правильный.