Правила UI Accessibility (A11Y)
Назначение
Документ фиксирует обязательные требования к доступности frontend-приложений Pin. Цель — обеспечить минимум WCAG 2.1 Level AA для всех экранов, поддержку клавиатуры, screen reader и пользователей с ограничениями моторики, зрения и когнитивных способностей.
Целевой стандарт
- WCAG 2.1 Level AA — минимум.
- WCAG 2.1 Level AAA — для критичных диалогов (вход, подтверждение деструктивных действий, экспорт).
- Keyboard-only navigation — приложение полностью используемо без мыши.
- Screen reader compatibility — VoiceOver (macOS/iOS), NVDA (Windows), TalkBack (Android), JAWS.
Базовые требования
Контрастность
| Элемент | Контраст к фону | Стандарт |
|---|
| Основной текст ≥ 18pt regular | 4.5:1 | AA |
| Крупный текст ≥ 24pt или ≥ 18pt bold | 3:1 | AA |
| UI-компоненты (border, focus ring) | 3:1 | AA |
| Disabled-элементы | Контраст можно снизить, но визуально читаемы | — |
Все цветовые токены валидируются на контрастность через axe-core в pre-commit hook.
Размер шрифта и масштабирование
- Минимальный размер текста — 14px (десктоп) и 16px (мобильный).
- Поддержка browser zoom до 200% без потери функциональности.
- Поддержка native font scaling в системных настройках.
Цвет — не единственный носитель смысла
- AlertLevel (
Green/Yellow/Red/Black) дублируется иконкой и текстовой меткой.
- Статусы валидации помечаются иконкой + цветом + текстом одновременно.
- Чарты в светофоре имеют паттерны для дальтоников (полоски/точки).
Клавиатурная навигация
Базовый порядок
| Действие | Клавиша | Поведение |
|---|
| Переход вперёд | Tab | По всем интерактивным элементам в логическом порядке |
| Переход назад | Shift+Tab | Обратный порядок |
| Активировать кнопку/ссылку | Enter или Space | Кнопки — обе; ссылки — Enter |
| Закрыть диалог/popover | Escape | Возвращает фокус к триггеру |
| Открыть command palette | Ctrl+K (или Cmd+K) | Глобально доступна |
| Поиск в таблице | Ctrl+F (или Cmd+F) | Открывает поиск таблицы (override browser default) |
Focus management
- Видимый focus ring обязателен (минимум 3px, контраст 3:1).
- При открытии модала фокус перемещается на первый интерактивный элемент модала.
- При закрытии модала фокус возвращается на триггер.
- Focus trap внутри модала —
Tab циклически переключается внутри.
- Skip-link «Перейти к основному содержимому» в начале каждой страницы.
Shortcuts
Сервисные shortcuts документируются в 04-frontends/pin-admin/07-shared/lib/keyboard-shortcuts.md. Базовый набор:
| Shortcut | Действие |
|---|
Ctrl+K / Cmd+K | Command palette |
Ctrl+/ | Помощь / список shortcuts |
Ctrl+S | Сохранить (в формах) |
Ctrl+Shift+S | Сохранить как черновик |
Ctrl+Enter | Submit формы |
Escape | Закрыть диалог / отменить inline edit |
g j | Перейти в Журналы (gmail-style) |
g d | Перейти в Дашборды |
g s | Перейти в Расписания |
ARIA roles и labels
Базовые роли
| Элемент | Роль | Атрибуты |
|---|
| Header app bar | role="banner" | — |
| Sidebar nav | role="navigation" aria-label="Главная навигация" | — |
| Main content | role="main" | — |
| Footer | role="contentinfo" | — |
| Modal | role="dialog" aria-modal="true" aria-labelledby="dialog-title" | Описание заголовка |
| Toast | role="status" (info) или role="alert" (error) | — |
| Tab list | role="tablist" + role="tab" + role="tabpanel" | aria-selected, aria-controls |
| Sortable column | aria-sort="ascending" / "descending" / "none" | — |
Labels
- Каждый input имеет связанный label через
<label for=""> или aria-labelledby.
- Иконочные кнопки имеют
aria-label.
- Decorative иконки помечаются
aria-hidden="true".
- Loading spinner имеет
role="status" + aria-live="polite" + текст «Загрузка...».
Live regions
| Тип | aria-live | Использование |
|---|
| Toast info | polite | Информационные сообщения |
| Toast error | assertive | Ошибки, требующие внимания |
| Validation errors | polite | Появление ошибок валидации |
| Progress updates | polite | Прогресс долгих операций |
Формы
- Каждое поле имеет label, не только placeholder.
- Required-поля помечаются
aria-required="true" и визуально (звёздочка + текст).
- Ошибка валидации связана с полем через
aria-describedby.
- При submit с ошибками фокус автоматически переходит на первое invalid поле.
- Сообщения об ошибке — текстовые, не только цветом.
- Disabled-кнопки имеют
aria-disabled="true" и причину disabled через aria-describedby или tooltip.
Таблицы
<table> со <thead> и <tbody>.
- Заголовки колонок —
<th scope="col">.
- Sortable колонки имеют
aria-sort + видимый индикатор.
- Pagination имеет
aria-label с текущей страницей.
- Empty state описан текстом, не только иконкой.
- Bulk actions описаны через
aria-label и доступны с клавиатуры.
Изображения и иконки
- Декоративные иконки:
aria-hidden="true".
- Иконки с смыслом:
aria-label="..." или <span class="sr-only">текст</span> рядом.
- Изображения с контентом:
<img alt="осмысленное описание">.
- Изображения, дублирующие текст рядом:
<img alt="">.
Reduced motion
- Все анимации >200ms имеют отключение через
@media (prefers-reduced-motion: reduce).
- Карусели/слайдеры не запускаются автоматически.
- Анимация фокуса должна оставаться (короткая) даже при reduced-motion.
Тестирование A11Y
| Инструмент | Использование | Этап |
|---|
eslint-plugin-jsx-a11y | Lint-проверки на этапе разработки | dev |
axe-core через @axe-core/react | Runtime checks в dev режиме | dev |
Playwright + @axe-core/playwright | E2E A11Y проверки | CI |
Storybook + @storybook/addon-a11y | Component-level A11Y | dev/CI |
| Manual screen reader testing | VoiceOver / NVDA для критичных flows | release |
| Manual keyboard testing | Полный обход без мыши для критичных flows | release |
Запрещено
- Использование
outline: none без замены на видимый focus ring.
tabindex="-1" на интерактивных элементах без причины.
tabindex > 0 (нарушает порядок навигации).
- Использование
<div onClick> без role="button" + tabIndex={0} + onKeyDown.
- Auto-focus в формах без явной необходимости.
- Auto-redirect/auto-refresh без user control.
- Использование цвета как единственного индикатора состояния.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы