Доступность (a11y) — не благотворительность и не «потом, если останется время». Это часть определения «продукт работает»: им должны пользоваться люди с клавиатуры, со скринридером, с увеличением. К тому же доступная разметка обычно и проще, и лучше для поиска. Продукт-инженер относится к a11y как к любому критерию качества — встроенному, а не приклеенному в конце.
Семантическая разметка
Девяносто процентов доступности — это правильные HTML-элементы. Браузер уже знает, что button кликается и фокусируется, nav — навигация, label связан с полем. Заменяя их на div с обработчиком, ты выбрасываешь это знание и потом дорого восстанавливаешь его через ARIA.
// плохо: div ничего не сообщает и не фокусируется
<div onClick={save}>Сохранить</div>
// хорошо: button кликается, фокусируется, доступен с клавиатуры
<button onClick={save}>Сохранить</button>
Правило: сначала ищи подходящий семантический элемент, и только если его нет — добавляй ARIA. Заголовки (h1–h3) идут по порядку, у полей есть label, у картинок — alt. Это фундамент, на котором держится всё остальное.
Клавиатура и фокус
Всё, что доступно мышью, должно работать с клавиатуры: переход по Tab, активация по Enter/Space, понятный видимый фокус. Семантические элементы дают это бесплатно; кастомные виджеты (выпадашки, модалки) — нет, и за ними следят руками: ловушка фокуса в модалке, возврат фокуса после закрытия, закрытие по Escape.
Видимый фокус (:focus-visible) не убирают «ради красоты» — без него пользователь клавиатуры теряется на странице. Это частый и легко исправимый дефект.
ARIA — по необходимости
ARIA-атрибуты (role, aria-label, aria-expanded) добавляют семантику там, где штатных элементов не хватает. Но первое правило ARIA — не использовать ARIA, если есть нативный элемент: <button> лучше, чем <div role="button" tabindex="0"> с ручной обработкой клавиш. Неверный ARIA хуже, чем никакой: он вводит скринридер в заблуждение. ARIA уместен для динамики, у которой нет HTML-аналога — например, aria-live для уведомлений, появляющихся без перезагрузки.
Тестирование доступности
Доступность проверяема, а не «на глаз». Автоматические проверки (axe, плагины для линтера) ловят типовое — отсутствие alt, контраст, неверный ARIA. А Testing Library поощряет доступность самой своей моделью: она ищет элементы по роли и доступному имени (getByRole("button", { name: "Сохранить" })), как это делает скринридер. Если тест не может найти кнопку по роли — её, скорее всего, не найдёт и вспомогательная технология.
const button = screen.getByRole("button", { name: "Сохранить" });
То есть тесты, написанные «как пользователь», заодно проверяют доступность.
Где это в UCP
Доступность — встроенный критерий качества frontend, не финальная полировка: правильная семантика, работа с клавиатуры, ARIA только по нужде, проверки в тестах. Это та же дисциплина «качество — часть готовности», что наблюдаемость и тесты в backend. Для продукт-инженера, который ведёт продукт до пользователя, «до пользователя» включает всех пользователей — и тесты по ролям делают это проверяемым.