Angular
Доступность и Angular Aria
Модальное окно открылось, но фокус остался на кнопке под ним. Пользователь скринридера не понимает, что появился диалог, табом уходит на ссылки за оверлеем, а сообщение об успешной отправке формы проговаривается только тем, кто его видит. Для миллионов людей с нарушениями зрения и моторики такой интерфейс просто не работает. Angular даёт два уровня инструментов: CDK a11y с готовыми утилитами (ловушка фокуса, живой анонсер) и Angular Aria - набор доступных по умолчанию примитивов, в которые правильные ARIA-атрибуты и клавиатура встроены изначально.
- Государственные и банковские сайты обязаны соответствовать WCAG по закону (в ряде стран это требование регуляторов)
- Angular Material построен поверх CDK a11y: его меню, диалоги и табы доступны с клавиатуры и для скринридеров из коробки
- FocusTrap из CDK удерживает фокус внутри открытого модального окна - без этого таб уводит пользователя за пределы диалога
- LiveAnnouncer проговаривает динамические сообщения (товар добавлен в корзину) тем, кто не видит экран
- Навигация с клавиатуры критична для людей, не использующих мышь, и заодно ускоряет работу опытных пользователей
Предварительные знания
- Привязки в шаблоне: property binding, атрибутные привязки, обработка событий
- Базовая семантика HTML: button, nav, заголовки, label у полей формы
- Идея о том, что часть пользователей работает без мыши и со скринридером
Доступность в Angular: от CDK к Angular Aria
Когда команда Angular строила Material, ей понадобились переиспользуемые низкоуровневые утилиты доступности, и так появился пакет @angular/cdk/a11y с FocusTrap, LiveAnnouncer и FocusMonitor (примерно 2018 год). Эти инструменты годами лежали в основе доступности Material. Следующий шаг - Angular Aria, набор headless-примитивов для построения собственных доступных компонентов (меню, табы, аккордеон, listbox), где паттерны WAI-ARIA Authoring Practices и клавиатурная навигация встроены, а внешний вид остаётся за разработчиком.
Основы a11y: семантика прежде ARIA
Доступность - это пригодность интерфейса для людей с разными возможностями: незрячих и слабовидящих (скринридеры), тех, кто не пользуется мышью (только клавиатура), с нарушениями моторики или восприятия цвета. Отраслевой ориентир - стандарт WCAG (Web Content Accessibility Guidelines). Первое и главное правило доступности парадоксально: лучший ARIA-атрибут - тот, который не понадобился, потому что использован правильный семантический элемент.
Элемент button бесплатно объявляет роль кнопки скринридеру, попадает в порядок табуляции и срабатывает по Enter и Пробелу. Чтобы повторить это поведение на div, пришлось бы вручную добавить role, tabindex и обработчики клавиш - три потенциальных источника ошибок вместо одного правильного тега. Поэтому семантика стоит прежде ARIA.
| Задача | Семантический элемент | Что даёт бесплатно |
|---|---|---|
| Действие | button | Роль, фокус, Enter/Пробел |
| Переход | a с href | Роль ссылки, фокус, переход |
| Навигация | nav | Ориентир для скринридера |
| Поле формы | label + input | Связь подписи и поля, клик по подписи |
Кликабельный div без role, tabindex и обработки клавиш невидим для скринридера и недоступен с клавиатуры. Это самая частая ошибка доступности. Прежде чем тянуться к ARIA, стоит спросить: нет ли HTML-элемента, который уже делает нужное.
Почему кнопку-действие лучше делать через button, чем через div с обработчиком click и подходящим стилем?
ARIA-атрибуты через привязки шаблона
Когда семантики HTML не хватает - например, у кастомного компонента или для передачи состояния, - на помощь приходят ARIA-атрибуты. Они сообщают вспомогательным технологиям роль, состояние и свойства элемента: aria-label даёт доступное имя, aria-expanded показывает, раскрыт ли блок, aria-pressed - нажата ли кнопка-переключатель. В Angular динамические значения ARIA задаются через атрибутную привязку с префиксом attr.
Здесь aria-expanded привязан к сигналу isOpen и меняется вместе с состоянием панели, поэтому скринридер всегда знает, раскрыта она или нет. aria-controls связывает кнопку с панелью по id. У иконочной кнопки без текста aria-label даёт доступное имя Закрыть - иначе скринридер прочитал бы лишь символ. Префикс attr нужен, потому что aria-атрибуты живут в DOM как атрибуты, а не как свойства.
Главное правило ARIA: не дублировать и не ломать семантику. Ставить role button на настоящий button бессмысленно, а role button на div без клавиатуры - вредно, потому что скринридер объявит кнопку, которой нельзя пользоваться. ARIA дополняет семантику там, где её нет, но не подменяет правильный элемент.
Неверный или устаревший ARIA хуже отсутствия ARIA. aria-expanded, забытый в значении false при открытой панели, прямо вводит пользователя скринридера в заблуждение. Поэтому состояние ARIA привязывают к реальному состоянию компонента, а не проставляют вручную один раз.
Иконочная кнопка отображает только символ крестика без текста. Как сделать её понятной для скринридера в Angular?
CDK a11y: FocusTrap и LiveAnnouncer
Часть задач доступности решается не атрибутами, а поведением, и для них есть пакет @angular/cdk/a11y. Две ключевые утилиты: FocusTrap удерживает фокус внутри области (модального окна), чтобы Tab не уводил пользователя за оверлей, и LiveAnnouncer проговаривает скринридеру динамические сообщения, которых нет в фокусе. Обе доступны как директива/сервис и решают проблемы, недостижимые одной разметкой.
Директива cdkTrapFocus замыкает Tab и Shift+Tab внутри диалога: дойдя до последней кнопки, фокус возвращается на первую, а не уходит на элементы за оверлеем. Флаг cdkTrapFocusAutoCapture ставит фокус внутрь при открытии. Роль dialog, aria-modal и aria-labelledby сообщают скринридеру, что это модальное окно и как оно называется. Без ловушки фокуса диалог недоступен с клавиатуры.
LiveAnnouncer кладёт текст в скрытую aria-live область, и скринридер проговаривает его, не перемещая фокус. Параметр polite означает дождаться паузы в речи; assertive прервёт немедленно для срочных сообщений. Это единственный способ сообщить незрячему пользователю о событии, которое визуально видно, но не получает фокус.
Какую проблему доступности модального окна решает FocusTrap (cdkTrapFocus) из CDK a11y?
Angular Aria и навигация с клавиатуры
Для построения собственных доступных компонентов - меню, табов, аккордеона, listbox - есть Angular Aria, набор headless-примитивов. Headless означает: примитив несёт поведение и доступность (правильные роли, ARIA-состояния, клавиатурную навигацию по паттернам WAI-ARIA), но не навязывает внешний вид. Разработчик получает корректную доступность бесплатно и стилизует компонент как угодно. Это снимает классическую дилемму между кастомным дизайном и доступностью.
Клавиатурная навигация - сердце доступных интерактивных компонентов, и у неё есть устоявшиеся соглашения. Tab переходит между виджетами, стрелки перемещают фокус внутри одного виджета (между пунктами меню, вкладками), Enter и Пробел активируют, Escape закрывает. Эти паттерны и встроены в примитивы Angular Aria, поэтому набор пунктов меню сразу управляется стрелками без ручного кода.
| Клавиша | Ожидаемое действие |
|---|---|
| Tab / Shift+Tab | Переход между виджетами по порядку |
| Стрелки | Перемещение фокуса внутри виджета (меню, табы) |
| Enter / Пробел | Активация элемента в фокусе |
| Escape | Закрытие меню, диалога, выпадающего списка |
Проверить доступность с клавиатуры можно без специальных инструментов: убрать руку с мыши и пройти весь сценарий только клавиатурой. Если до элемента нельзя добраться Tab, активировать Enter или закрыть Escape - компонент недоступен. Примитивы Angular Aria закрывают эти паттерны по умолчанию, но самописные виджеты стоит проверять именно так.
Что означает headless-природа примитивов Angular Aria для построения доступного меню или табов?
Связь с другими темами
Урок про то, чтобы интерфейс работал для всех. Связи:
- Шаблоны и привязки — ARIA-атрибуты и роли задаются через привязки в шаблоне компонента
- Тестирование — Playwright и harness обращаются к элементам по роли и имени, проверяя доступность
- Строгая типизация — Доступность - слой качества рядом с типобезопасностью в модуле архитектуры
Итог
- Доступность (a11y) делает интерфейс пригодным для людей со скринридером, без мыши и с другими особенностями; ориентир - стандарт WCAG
- Семантический HTML - первый и главный инструмент: правильный элемент несёт роль и поведение бесплатно
- ARIA-атрибуты дополняют семантику там, где её не хватает, но не заменяют правильный элемент
- CDK a11y даёт FocusTrap (удержание фокуса в диалоге), LiveAnnouncer (озвучивание динамики) и FocusMonitor
- Angular Aria - headless-примитивы доступных компонентов с встроенными ARIA и клавиатурой, оформление за разработчиком
- Клавиатурная навигация (Tab, стрелки, Enter, Escape) обязательна: всё, что доступно мышью, должно работать с клавиатуры
Связанные уроки
- ng-04-templates-binding — ARIA-атрибуты ставятся через привязки в шаблоне, нужна модель template binding
- ng-40-testing — Playwright обращается к элементам по роли и имени, поэтому доступность проверяется e2e-тестами