EverProduct
Фронтенд

Этап 02 · Страница, которая держит удар

Второй интерфейс: доступность в коде

У твоей страницы два интерфейса — тот, что на экране, и тот, что машины читают вслух. Второй порождается твоим кодом независимо от того, думал ты о нём или нет.

Выключи монитор и открой собственный сайт со скринридером. Большинство разработчиков этого не делали никогда, и первые тридцать секунд просвещают так, как не может ни одна статья.

Слышишь ты примерно следующее: «кнопка. кнопка. кликабельно. ссылка. изображение, 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. Запускай их. А потом помни цифру, которая имеет значение: они ловят примерно треть настоящих проблем. Осмыслен ли порядок табуляции, понятны ли объявления, возвращает ли модалка фокус, — этого не оценит ни один инструмент.

Отчего зелёный аудит превращается в хрестоматийную иллюзию компетентности в смысле сферы «Как учиться»: приятный сигнал, измеряющий не то, и уверенность, которая не заработана. Три проверки, которые действительно работают, — ручные, и ни одна не занимает много времени:

  1. Отключи мышь. Сделай основную задачу только с клавиатуры.
  2. Пройди табом и следи за кольцом фокуса. Всегда ли видно, где ты? Совпадает ли порядок с визуальным?
  3. Включи скринридер на пять минут. VoiceOver встроен в macOS и iOS, «Экранный диктор» — в Windows, NVDA бесплатен. Пользоваться им ты будешь отвратительно — и не в этом дело. Дело в том, чтобы услышать собственную страницу.

На практике

Привяжи клавиатурный проход к тому, что и так делаешь. Правило привычек из сферы «Как учиться»: сигнал важнее намерения. «Закончил компонент — прошёл его табом» — сигнал, который реально наступает. «Заботиться о доступности» — нет.

Прочитай дерево доступности только что собранного компонента. Десять секунд, и видно ровно то, что ты выпустил.

Почини три вещи, покрывающие бо́льшую часть: у каждого интерактивного элемента есть имя, фокус виден везде, ничему не нужна мышь.

Учи по одному сочетанию клавиш скринридера в неделю. Двадцать минут суммарно, разнесённых по времени, — это осознанная практика на том, чего ты не умеешь, вместо чтения про то, что уже умеешь.

Добавь доступность в определение готовности, в тот же список, где «работает на мобильном». То, чего нет в этом списке, не делается никогда.

Проверь себя

Закрой статью и ответь своими словами:

  1. Что такое дерево доступности и что оно хранит про каждый элемент?
  2. Почему role="button" на div хуже, чем оставить его без роли?
  3. Откуда берётся доступное имя и назови три частых способа его потерять.
  4. Чем плох outline: none и что использовать вместо него?
  5. Когда фокусом должен управлять твой код?
  6. Зачем нужна живая область и когда оправдан assertive?
  7. Почему пройденный автоматический аудит — иллюзия компетентности и какие три ручные проверки его заменяют?

Коротко

  • У каждой страницы есть второй интерфейс, порождённый разметкой в дерево доступности — роль, имя, значение, состояние, — и его можно прочитать в инструментах разработчика.
  • Нативные элементы дают бо́льшую его часть бесплатно; лучшая ARIA — отсутствие ARIA.
  • ARIA меняет то, что произносят, а не то, что происходит: роль без поведения — обещание, которое элемент не сдержит.
  • Доступные имена — самый частый провал: кнопки-иконки, повторяющиеся «подробнее», пропущенные alt, поля без подписей.
  • Фокус — это интерфейс для тех, кто без мыши: держи его видимым, порядок осмысленным, управляй им при модалках, удалениях и смене экрана, добавь ссылку-пропуск.
  • Асинхронные изменения объявляй живой областью, которая существует до изменения.
  • Для сложных виджетов бери нативные элементы или проверенную библиотеку; самописные комбобоксы — место, где страницы становятся непригодными.
  • Автоматика ловит около трети; настоящие проверки — работа только с клавиатуры, наблюдение за кольцом фокуса и пять минут со скринридером.