Второй интерфейс: доступность в коде
У твоей страницы два интерфейса — тот, что на экране, и тот, что машины читают вслух. Второй порождается твоим кодом независимо от того, думал ты о нём или нет.
Выключи монитор и открой собственный сайт со скринридером. Большинство разработчиков этого не делали никогда, и первые тридцать секунд просвещают так, как не может ни одна статья.
Слышишь ты примерно следующее: «кнопка. кнопка. кликабельно. ссылка. изображение, 4382 точка jpg. кликабельно. поле редактирования. кнопка».
Это и есть твой второй интерфейс. Не особый режим, который можно включить потом, — он уже существует, он порождён твоей разметкой, и прямо сейчас часть людей пользуется именно им. Сфера «UX-дизайн» объясняет, почему это важно и кого касается. Эта статья — про механику: что именно в твоём коде производит второй интерфейс и как сделать так, чтобы он перестал быть шумом.
Дерево доступности
Рядом с DOM браузер строит дерево доступности — ту же страницу, сведённую к тому, что нужно вспомогательным технологиям: для каждого элемента роль (что это), имя (как называется), значение и состояние (нажато, раскрыто, отключено, невалидно).
На него можно посмотреть. В инструментах разработчика любого браузера есть соответствующая панель, и чтение там собственной страницы — самый быстрый способ увидеть, что ты на самом деле собрал. Элементы без роли, элементы управления без имени, целые пропавшие области — всё видно за несколько секунд.
Всё дальнейшее — лишь способы правильно заполнить это дерево.
Первое правило: бери правильный элемент
Бо́льшая часть доступности решается до того, как в разговоре появляется ARIA. У элемента button уже есть роль «кнопка», он берёт фокус, реагирует на Enter и пробел и объявляет свою подпись. nav, main и h2 уже дают ориентиры и оглавление.
Отсюда правило, экономящее больше всего сил: лучшая ARIA — отсутствие ARIA. Сначала всегда тянись к нативному элементу и добавляй ARIA только там, где у платформы нет эквивалента.
Причина — факт, который узнают слишком поздно: ARIA меняет то, что произносят, а не то, что происходит. Повесив role="button" на div, ты заставишь скринридер сказать «кнопка» — а див по-прежнему не берёт фокус, по-прежнему не срабатывает на Enter, по-прежнему не в порядке табуляции. Ты дал обещание, которое элемент не может сдержать, и это хуже, чем не сказать ничего.
ARIA — это табличка, а не провод. Она переименовывает вещь, но ничего не подключает.
Короткую версию официальных правил стоит запомнить: не используй ARIA там, где есть нативный элемент; не переопределяй нативную семантику (role="heading" на кнопке не поможет никому); всё интерактивное обязано работать с клавиатуры; никогда не вешай aria-hidden на то, что может получить фокус; и у каждого интерактивного элемента должно быть доступное имя.
Имена: самый частый провал
Доступное имя элемента — то, что произносят. Оно берётся, грубо говоря, в таком порядке: из связанной label, из aria-labelledby, из aria-label или из собственного текста элемента.
Провалы предсказуемы и повсеместны:
- Кнопки из одной иконки. Кнопка закрытия, внутри которой только SVG, объявляется как «кнопка».
aria-label="Закрыть"чинит это десятком символов. - Двенадцать «подробнее». Пользователи скринридеров часто ходят по странице, вызывая список всех ссылок. Двенадцать одинаковых «подробнее» — это список из двенадцати непригодных вариантов. Пусть текст ссылки несёт, куда она ведёт.
- Картинки. Отсутствующий
altприводит к чтению имени файла. Декоративной картинке нужен пустойalt, что означает «пропустить». - Поля формы с плейсхолдером и без подписи. Разобрано в статье про формы и по-прежнему самый распространённый дефект в вебе.
Фокус — это вся история про клавиатуру
Для тех, кто не пользуется мышью — людей с клавиатуры, пользователей скринридеров, людей с тремором и всех, у кого только что умер тачпад, — интерфейс и есть фокус.
Никогда не убирай обводку фокуса, не заменив её. outline: none встречается в стилях чаще любого другого бага доступности на земле. Если стандартное кольцо кажется уродливым, нарисуй своё через :focus-visible — оно показывается при работе с клавиатуры и не показывается при клике мышью.
Порядок фокуса идёт по DOM, поэтому визуальный порядок, полученный перестановкой средствами CSS, даёт табуляцию, скачущую по экрану. Положительные значения tabindex делают только хуже; нужны ровно два значения: 0 (фокусируется по порядку) и -1 (фокусируется только скриптом).
Управляй фокусом, когда страница меняется под ним. Открыл модалку — переведи фокус внутрь, удержи его там, верни на кнопку при закрытии. Удалил строку, на которой стоял фокус, — переведи его в осмысленное место, иначе человека выбросит в начало документа. Перешёл на другой экран в одностраничном приложении — ничего не перезагрузилось, поэтому о новой странице никто не объявит, пока ты сам этого не сделаешь.
Добавь ссылку-пропуск. Одна ссылка в начале, видимая при фокусе, ведущая к основному содержимому, чтобы человек с клавиатуры не проходил сорок пунктов меню на каждой странице.
Объявлять то, что изменилось
Зрячий пользователь замечает всплывшее в углу уведомление. Пользователь скринридера не замечает ничего: изменение в DOM беззвучно, пока ты не пометил изменяющуюся область.
Для этого существуют живые области: aria-live="polite" объявляет после того, как закончится текущая речь, и подходит почти для всего — количество найденного, «сохранено», сводка ошибок. aria-live="assertive" перебивает, и его стоит беречь для того, что действительно не может подождать.
Три практические заметки: контейнер должен существовать в DOM до изменения содержимого, сообщение должно быть коротким, и не вешай живую область на то, что обновляется непрерывно, если не хочешь построить машину, перебивающую саму себя.
Компоненты, на которых калечатся
У части виджетов нет нативного аналога, и они по-настоящему трудны: комбобокс с автодополнением, вкладки, деревья, кастомные селекты, календари, перетаскивание, карусели. У каждого есть описанный клавиатурный контракт — какая стрелка что делает, что делает Escape, о чём объявляют, — и сделать это правильно требует настоящей работы.
Честный совет: бери нативные элементы там, где они есть (dialog, details, настоящий select), а для остального используй проверенную библиотеку доступных компонентов вместо самописного. Самодельный комбобокс — один из самых надёжных способов сделать страницу непригодной, будучи уверенным, что сделал нечто умное.
Остаток списка
Контраст. Тексту нужно отношение 4.5:1 к фону (3:1 для крупного), и границам элементов управления тоже. Инструменты разработчика показывают число; это не вопрос мнения.
Никогда только цветом. Красная рамка у невалидного поля невидима для многих. Добавь иконку, текст или и то и другое — починка в самом сообщении.
Цели для касания около 44 пикселей, с промежутками.
prefers-reduced-motion — у части людей анимация вызывает настоящую тошноту и головокружение. Один медиазапрос, и движение меняется на плавное появление.
Язык. lang у элемента html и у любого элемента на другом языке, иначе скринридер произнесёт его не той фонетикой.
Масштаб 200% без потери содержимого и функций. Это нужно людям куда чаще, чем скринридер, и ломается куда чаще.
Проверки: дешёвые, потом честные
Автоматические инструменты — axe, Lighthouse, встроенный аудит браузера — занимают минуту и ловят вполне реальный набор ошибок: пропущенные alt, низкий контраст, отсутствующие имена, невалидную ARIA. Запускай их. А потом помни цифру, которая имеет значение: они ловят примерно треть настоящих проблем. Осмыслен ли порядок табуляции, понятны ли объявления, возвращает ли модалка фокус, — этого не оценит ни один инструмент.
Отчего зелёный аудит превращается в хрестоматийную иллюзию компетентности в смысле сферы «Как учиться»: приятный сигнал, измеряющий не то, и уверенность, которая не заработана. Три проверки, которые действительно работают, — ручные, и ни одна не занимает много времени:
- Отключи мышь. Сделай основную задачу только с клавиатуры.
- Пройди табом и следи за кольцом фокуса. Всегда ли видно, где ты? Совпадает ли порядок с визуальным?
- Включи скринридер на пять минут. VoiceOver встроен в macOS и iOS, «Экранный диктор» — в Windows, NVDA бесплатен. Пользоваться им ты будешь отвратительно — и не в этом дело. Дело в том, чтобы услышать собственную страницу.
На практике
Привяжи клавиатурный проход к тому, что и так делаешь. Правило привычек из сферы «Как учиться»: сигнал важнее намерения. «Закончил компонент — прошёл его табом» — сигнал, который реально наступает. «Заботиться о доступности» — нет.
Прочитай дерево доступности только что собранного компонента. Десять секунд, и видно ровно то, что ты выпустил.
Почини три вещи, покрывающие бо́льшую часть: у каждого интерактивного элемента есть имя, фокус виден везде, ничему не нужна мышь.
Учи по одному сочетанию клавиш скринридера в неделю. Двадцать минут суммарно, разнесённых по времени, — это осознанная практика на том, чего ты не умеешь, вместо чтения про то, что уже умеешь.
Добавь доступность в определение готовности, в тот же список, где «работает на мобильном». То, чего нет в этом списке, не делается никогда.
Проверь себя
Закрой статью и ответь своими словами:
- Что такое дерево доступности и что оно хранит про каждый элемент?
- Почему
role="button"наdivхуже, чем оставить его без роли? - Откуда берётся доступное имя и назови три частых способа его потерять.
- Чем плох
outline: noneи что использовать вместо него? - Когда фокусом должен управлять твой код?
- Зачем нужна живая область и когда оправдан
assertive? - Почему пройденный автоматический аудит — иллюзия компетентности и какие три ручные проверки его заменяют?
Коротко
- У каждой страницы есть второй интерфейс, порождённый разметкой в дерево доступности — роль, имя, значение, состояние, — и его можно прочитать в инструментах разработчика.
- Нативные элементы дают бо́льшую его часть бесплатно; лучшая ARIA — отсутствие ARIA.
- ARIA меняет то, что произносят, а не то, что происходит: роль без поведения — обещание, которое элемент не сдержит.
- Доступные имена — самый частый провал: кнопки-иконки, повторяющиеся «подробнее», пропущенные
alt, поля без подписей. - Фокус — это интерфейс для тех, кто без мыши: держи его видимым, порядок осмысленным, управляй им при модалках, удалениях и смене экрана, добавь ссылку-пропуск.
- Асинхронные изменения объявляй живой областью, которая существует до изменения.
- Для сложных виджетов бери нативные элементы или проверенную библиотеку; самописные комбобоксы — место, где страницы становятся непригодными.
- Автоматика ловит около трети; настоящие проверки — работа только с клавиатуры, наблюдение за кольцом фокуса и пять минут со скринридером.