DarkRiDDeR12 мин

Доступный интерфейс: проверить кнопку до того, как она станет «готовой»

FrontendКачество

Компонент может пройти визуальный review и всё равно не дать человеку закончить действие. Типичный симптом: панель настроек открывается мышью, но с клавиатуры непонятно, куда попал фокус; после сохранения меняется текст, а состояние элемента не имеет объявленного имени, роли или статуса. Цена ошибки не в «плохом ARIA». Человек не может уверенно выбрать настройку, исправить неверный ввод или понять, завершилось ли действие.

В апреле 2022 года я бы не начинал с набора атрибутов. Сначала надо описать один сценарий как контракт: какой элемент получает фокус, какое у него имя и роль, что меняется после действия и куда возвращается фокус. Это связывает новую для автора область доступности с предыдущими статьями о рендеринге: видимый кадр — не весь интерфейс. Отдельно важная граница: пример ниже — учебная in-memory модель. Он не запускает DOM, браузер, клавиатуру или ассистивную технологию и не доказывает, что конкретный screen reader что-то произнёс.

Один сценарий вместо общего чек-листа

Возьмём панель «Настроить уведомления». В исходном состоянии фокус стоит на кнопке открытия. После открытия — на первом варианте канала. Неверное значение не меняет сохранённый канал и возвращает маршрут на элемент, где можно исправиться. Корректное сохранение закрывает панель, возвращает фокус на trigger и формирует declared status. Последнее слово принципиально: команда заявляет, какое состояние должна передавать разметка, но не подменяет этим наблюдение реальной программы чтения с экрана.

Контракт одного интерактивного сценария
ТочкаСимптом без контрактаПроверяемое свойствоДействие
Открыть панельфокус остаётся на декоративной иконкеимя «Настроить уведомления», роль button, следующий focus targetиспользовать нативную кнопку или реализовать полный button contract
Выбрать каналвидна галочка, но неясно, что выбраноимя группы, role radiogroup, один declared checked valueсвязать визуальный выбор с тем же state
Ошибкасообщение есть только цветом или рядом с мышьюinvalid branch не меняет saved value и имеет named focus targetсохранить причину и вернуть маршрут к исправлению
Сохранитьпанель исчезает, фокус теряетсяreturn target и declared statusвернуть фокус на trigger, оставить способ отменить изменение

Сначала семантика, затем внешний вид

Нативный button, input и label дают часть поведения сразу. Если проект заменяет кнопку на div ради стиля, он берёт на себя не только role. Потребуются name, focusability, активация с клавиатуры, состояние и тестируемый порядок. WAI-ARIA Authoring Practices прямо отделяет role от поведения: роль не превращает произвольный элемент в полноценную нативную кнопку. Поэтому минимальное решение часто проще: оставить нативный host, а class и layout строить вокруг него.

Это не означает, что любой native element закрывает задачу. У кнопки может быть пустое или двусмысленное имя, состояние может обновляться в другом месте, а фокус — уходить после unmount. WCAG 2.1 задаёт проверяемые критерии для keyboard, focus order, name/role/value и status messages. Они полезны как карта свойств, но не назначают имя вашего state store и не выбирают момент закрытия панели. Эти решения остаются у компонента и должны быть видны в его тесте.

Учебная fixture: проверяем порядок, а не браузер

В пакете есть короткая детерминированная модель. Она хранит channel, savedChannel, pendingUndo и focus как локальные строки. После открытия пробует недопустимое значение carrier-pigeon, затем выбирает sms, сохраняет и отменяет сохранение. Значения preferences-trigger и notification-email — проектные labels маршрута, а не id из существующей страницы. Это позволяет проверить смысл изменения без выдуманного browser test.

node web/scripts/upgrade-2022-04.mjs --verify-fixture
// PASS fixture: 16/16 assertions

// Модель проверяет declared name, role, focusRoute, invalid branch и undo.
// Она не создаёт DOM, не нажимает Tab и не получает речь screen reader.

Шестнадцать assertions охраняют именно границу модели: DOM не создан, клавиатурное событие не отправлено, ассистивная технология не наблюдалась. Остальные assertions проверяют name/role, declared focus route, вывод selected state из channel, invalid branch, возврат к trigger и одноразовый undo. Если в рефакторинге invalid branch начнёт менять saved value, команда увидит поломку правила. Если же реальная страница не показывает focus ring, fixture не даст ложного PASS: для этого нужна отдельная проверка в поддерживаемом браузере.

Последовательность учебного маршрута фокуса: кнопка «Настроить уведомления» ведёт к Email; недопустимый выбор возвращает к Email, а корректное сохранение возвращает на исходную кнопку. Рядом отмечено, что это declared route модели, не захват браузерного фокуса.
Маршрут делает явными две ветки, которые визуальный макет обычно скрывает: исправление неверного ввода и возврат после закрытия панели.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Компонент выглядит законченным, но Tab не даёт предсказуемой точки входа, выбор не имеет понятного имени или после save фокус исчезает.
  2. Причина. Визуальное состояние и semantic contract принадлежат разным веткам кода; роль добавили после разметки, а return focus не описали.
  3. Проверка семантики. Для trigger, группы вариантов, каждого выбора и статуса запишите name, role и изменяемое state. Если свойство нельзя назвать, его пока нельзя надёжно проверить.
  4. Проверка маршрута. Нарисуйте путь: вход, invalid branch, успешное закрытие. Для реальной страницы пройдите его клавиатурой в поддерживаемых браузерах и отдельно зафиксируйте результат; fixture этого не делает.
  5. Действие. Сначала замените ложный custom control нативным либо добавьте полный contract. Затем сделайте один небольшой test на return focus и один на invalid branch.
  6. Откат. Изменение состояния должно сохранять предыдущее значение до commit. В модели undo возвращает email и одноразово очищает pending action; реальный продукт всё равно должен отдельно определить срок и UX отмены.

Объявление — это данные, а не обещание результата

После сохранения компоненту полезно сформировать состояние вроде «Канал уведомлений сохранён: SMS». Но нельзя записать в отчёт «screen reader произнёс фразу», если никто не проверял конкретную комбинацию браузера и технологии. В fixture есть только declaredAnnouncement и отметка declared-not-observed. Она проверяет, что код не потерял сообщение при переходе, но не моделирует очередь live region, язык интерфейса, настройки пользователя или фактическую речь.

Для production-проверки это превращается в маленький план. Сначала посмотреть DOM и computed accessibility tree выбранного браузера; затем пройти Tab, Shift+Tab, Enter и Space там, где они предусмотрены паттерном; затем проверить выбранные сочетания assistive technology и браузера с заранее записанным ожидаемым состоянием. Результат такой проверки — новый артефакт с версиями и ограничениями. Его нельзя заранее вывести из ARIA-атрибута или из учебной модели.

Ограничение и следующий шаг

Этот маршрут покрывает один control contract, а не доступность продукта. Он не измеряет контраст, не проверяет локализацию, touch target, виртуальный курсор, порядок во всех модальных окнах и не заменяет исследование с пользователями. Даже полное прохождение WCAG-критериев не позволяет заявить, что опыт удобен для каждого человека. Но контракт убирает более раннюю и дешёвую ошибку: компонент перестаёт быть «визуально готовым» без имени, маршрута и обратимого результата.

Следующий практический шаг — выбрать один часто используемый компонент, собрать таблицу его name/role/state/focus и зафиксировать один keyboard route до любого редизайна. Если найдётся несоответствие, исправляйте его малым обратимым изменением: native host, явный return target или связанный status. После изменения повторите реальную проверку и добавьте её результат рядом с кодом, не заменяя его общим словом «доступно».

Историческая граница апреля 2022

Статья опирается на WCAG 2.1 Recommendation от 5 июня 2018 года и WAI-ARIA Authoring Practices 1.2 от 29 ноября 2021 года. В декабре 2021-го WAI-ARIA 1.2 имела статус Candidate Recommendation Draft; Recommendation 2023 года здесь не используется как будто она уже существовала. Учебные labels, undo policy и status text — решения пакета, а не требования W3C и не отчёт о production-дефекте.

Проверяемые источники