Вернёмся к карточке кроссовок и зададим неудобный вопрос: как программа чтения с экрана узнаёт, что на странице есть кнопка добавления в корзину? Она не видит пикселей и не читает CSS. Значит, где-то есть представление интерфейса, где сказано не как элемент выглядит, а что он такое.
Такое представление браузер строит сам. Помимо DOM (дерева элементов) и render tree (дерева отрисовки) он держит accessibility tree - дерево доступности. Это очищенная модель, где каждый значимый элемент описан не разметкой, а смыслом: чем является, как называется, в каком состоянии, с чем связан. С этим деревом, а не с картинкой, работают программы чтения, голосовое управление и другие assistive technologies.
Смысл элемента в дереве складывается из нескольких свойств. Роль (role) - что это такое: кнопка, ссылка, заголовок, поле ввода, флажок. Имя (accessible name) - как назвать вслух: у нашей кнопки имя Add to cart. Состояние (state) - что с ним сейчас: нажата, выбрана, свёрнута, недоступна. Отношения (relationships) связывают элементы: поле подписано меткой, ошибка относится к полю. Программа чтения произносит четвёрку: button, Add to cart, disabled.
Отдельного пояснения заслуживает имя. Оно не берётся из воздуха - браузер вычисляет его по фиксированному порядку источников (accessible name computation): сначала явные подписи вроде aria-labelledby и aria-label, затем текст самого элемента, затем запасные варианты вроде title. Вывод один: у каждого интерактивного элемента имя обязано быть, и лучше из видимого текста, а не из скрытых костылей.
Чтобы это перестало быть абстракцией, представим один узел дерева доступности так, как его увидит вспомогательная технология.
Наивная модель разработчика: если на экране всё выглядит правильно, значит, всё в порядке. Но дерево доступности живёт своей жизнью. Кликабельный div выглядит кнопкой, а в дереве это безымянный контейнер без роли; иконка-корзина без подписи - пустой узел; динамически подставленный текст ошибки виден на экране, но в дерево не попадает, и его никто не озвучит. Картинка и смысл разошлись - и пользователь вспомогательной технологии получает именно смысл.
Чтобы рассуждать о качестве системно, WCAG (Web Content Accessibility Guidelines) вводит четыре принципа - POUR. Это не набор придирок, а четыре вопроса к любому элементу: можно ли получить его информацию другим каналом; можно ли управлять им без точного движения мышью; понятны ли поведение и ошибки; честно ли он выражен платформе.
| Принцип POUR | Практический вопрос | Пример проблемы |
|---|---|---|
| Perceivable (воспринимаемость) | Можно ли получить информацию другим каналом? | Иконка без имени, видео без субтитров. |
| Operable (управляемость) | Можно ли управлять без точного движения мышью? | Меню невозможно открыть с клавиатуры. |
| Understandable (понятность) | Понятны ли поведение, ошибки и язык? | Форма сбрасывается без объяснения. |
| Robust (надёжность) | Корректно ли интерфейс выражен платформе? | Кликабельный div без роли и клавиатуры. |
WCAG существует в уровнях соответствия - A, AA и AAA - по нарастанию строгости. Разумная цель большинства проектов - WCAG 2.2, уровень AA: он покрывает то, что реально мешает пользоваться продуктом, и достижим без искажения дизайна. Уровень AAA не стоит объявлять обязательным для всего сайта: часть критериев зависит от типа контента и неприменима повсеместно. Планка AA - не потолок, а честный минимум.
Провал всегда выглядит одинаково: на экране всё хорошо, а в дереве доступности - дыра. Кнопка Add to cart на div с onclick для зрячего работает, но в дереве у неё нет ни роли button, ни имени, ни состояния, ни связи с ценой и наличием рядом. Программа чтения проходит мимо, голосовое управление не находит цель по имени, а формально страница рабочая. Вывод, из которого растёт следующая глава: доступность решается не поверх готовой вёрстки, а выбором правильных элементов, у которых роль, имя и состояние есть по умолчанию.
button "Add to cart" [ state: disabled=false ]
└ related: describedby -> price, availability