Accessibility (A11y): создание инклюзивных веб-приложений

Практическое руководство по разработке доступных интерфейсов для всех пользователей

По данным Всемирной организации здравоохранения, более 1,3 миллиарда человек в мире живут с той или иной формой инвалидности. В России, по данным Росстата, это около 11 миллионов человек. Если ваше веб-приложение недоступно для них — вы теряете аудиторию, рискуете нарушить законодательство и, что важнее, лишаете людей возможности пользоваться вашим продуктом. Accessibility (доступность, A11y) — это не благотворительность, это качество продукта. Инклюзивный дизайн затрагивает не только пользователей с постоянными ограничениями. Сломанная рука, яркое солнце на экране телефона, шумная среда, плохое интернет-соединение — в этих ситуациях каждый человек сталкивается с барьерами, которые решает продуманная доступность. По исследованию Microsoft Inclusive Design, временные и ситуационные ограничения охватывают значительно большую аудиторию, чем постоянные. В этой статье — практическое руководство: что конкретно нужно реализовать в коде, как тестировать, какие инструменты использовать и какие ошибки встречаются чаще всего в российских проектах. Без абстрактных рассуждений — только конкретные решения. Стандарты WCAG 2.1 и 2.2: что обязательно знать разработчику Web Content Accessibility Guidelines (WCAG) — международный стандарт доступности, разработанный W3C. Версия 2.1 актуальна с 2018 года, версия 2.2 вышла в 2023-м. Стандарт строится на четырёх принципах: воспринимаемость, управляемость, понятность, надёжность (POUR). Уровни соответствия: A (базовый), AA (стандартный, к которому стремится большинство), AAA (расширенный). Для коммерческих проектов цель — уровень AA. Именно этот уровень закреплён в большинстве государственных требований и международных стандартов. В России ГОСТ Р 52872-2019 регулирует требования к доступности интернет-ресурсов для федеральных органов власти. Для коммерческих проектов прямых санкций пока нет, но в 2023 году несколько крупных российских компаний столкнулись с судебными исками от пользователей с нарушениями зрения — прецеденты уже есть. Ключевые критерии WCAG 2.1 уровня AA, которые нарушаются чаще всего: 1.4.3 — Контрастность текста: минимум 4.5:1 для обычного текста, 3:1 для крупного (от 18pt или 14pt жирного) 1.4.11 — Контрастность элементов интерфейса: 3:1 для кнопок, инпутов, иконок 2.1.1 — Клавиатурная доступность: все функции должны быть доступны с клавиатуры 4.1.3 — Статусные сообщения: уведомления должны озвучиваться скринридером без переноса фокуса Семантическая HTML-разметка как основа доступности Большинство проблем с доступностью решаются правильной семантикой — ещё до подключения JavaScript и ARIA. Скринридеры (NVDA, JAWS, VoiceOver) строят дерево доступности на основе HTML-элементов. Если разработчик использует div и span там, где должны быть семантические теги, скринридер не может корректно интерпретировать структуру страницы. Типичная ошибка — кнопки, сделанные из div : !-- Неправильно: скринридер не распознает как кнопку -- div class="btn" onclick="submit()" Отправить /div !-- Правильно: нативный элемент с встроенной семантикой -- button type="submit" Отправить /button Нативный button автоматически: фокусируется с клавиатуры, активируется Enter и Space, объявляется скринридером как «кнопка», поддерживает состояние disabled . Самодельная кнопка из div требует добавления role="button" , tabindex="0" , обработчиков клавиш — и всё равно будет уступать нативному элементу. Семантическая структура страницы через landmark-элементы: header nav aria-label="Основная навигация" ul li a href="/" Главная /a /li li a href="/services" Услуги /a /li /ul /nav /header main h1 Заголовок страницы /h1 article h2 Раздел статьи /h2 p Контент... /p /article /main aside aria-label="Дополнительные материалы" !-- Сайдбар -- /aside footer !-- Подвал -- /footer Пользователи скринридеров используют горячие клавиши для перехода между landmark-регионами — это ускоряет навигацию так же, как зрячие пользователи используют визуальное сканирование страницы. ARIA: когда и как использовать правильно ARIA (Accessible Rich Internet Applications) — набор атрибутов, которые расширяют семантику HTML для сложных компонентов интерфейса: вкладок, модальных окон, аккордеонов, автодополнения. Первое правило ARIA: не используй ARIA, если есть нативный HTML-элемент с нужной семантикой. Три категории ARIA-атрибутов: Роли ( role ): определяют тип элемента — role="dialog" , role="tabpanel" , role="alert" Свойства ( aria-* ): описывают характеристики — aria-label , aria-describedby , aria-required Состояния ( aria-* ): отражают текущее состояние — aria-expanded , aria-checked , aria-disabled Пример доступного модального окна: div role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description" h2 id="modal-title" Подтверждение удаления /h2 p id="modal-description" Это действие необратимо. Вы уверены? /p button autofocus Отмена /button button Удалить /button /div Критически важная деталь: при открытии модального окна фокус должен переходить внутрь него, а при закрытии — возвращаться на элемент, который его открыл. Фокус не должен «утекать» за пределы модального окна (focus trap). // Реализация focus trap на React function useFocusTrap(ref) { useEffect(() => { const element = ref.current; const focusable = element.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; function handleKeydown(e) { if (e.key !== 'Tab') return; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } } element.addEventListener('keydown', handleKeydown); first?.focus(); return () => element.removeEventListener('keydown', handleKeydown); }, [ref]); } Управление с клавиатуры: паттерны навигации Клавиатурная доступность критична не только для пользователей с моторными нарушениями. Опытные пользователи, разработчики, люди с временными травмами рук — все они периодически используют клавиатуру как основной инструмент навигации. По данным WebAIM Screen Reader Survey 2023, около 70% пользователей скринридеров используют клавиатуру как основной способ взаимодействия. Базовые паттерны клавиатурной навигации согласно ARIA Authoring Practices Guide (APG): Вкладки (Tabs) : Tab — перемещение между табами, стрелки — переключение внутри группы вкладок Меню (Menu/Menubar) : Enter — открыть, Escape — закрыть, стрелки — навигация по пунктам Аккордеон : Enter/Space — раскрыть/свернуть панель Комбобокс (Combobox) : Enter — выбрать, Escape — закрыть список, стрелки — перемещение Управление видимостью фокуса — деликатный момент. Дизайнеры часто просят убрать outline у фокусированных элементов ради эстетики. Правильный компромисс — :focus-visible , который показывает outline только при клавиатурной навигации, но не при клике мышью: /* Неправильно: убирает фокус для всех */ button:focus { outline: none; } /* Правильно: скрывает только при клике мышью */ button:focus:not(:focus-visible) { outline: none; } button:focus-visible { outline: 3px solid #005fcc; outline-offset: 2px; border-radius: 4px; } :focus-visible поддерживается всеми современными браузерами начиная с 2022 года. Для IE (если ещё актуально) есть polyfill от WICG. Проверить порядок фокуса просто: закройте мышь и пройдите всё приложение клавишей Tab. Если в какой-то момент непонятно, где находится фокус, или он «прыгает» в неожиданное место — это баг доступности. Доступность форм: подробные рекомендации Формы — наиболее частый источник проблем с доступностью. Исследование WebAIM Million (анализ миллиона домашних страниц, 2024) показывает: у 67% форм есть как минимум один инпут без label. Это делает форму практически непригодной для пользователей скринридеров. Правильная связь label с полем: !-- Метод 1: атрибут for -- label for="email" Email /label input type="email" id="email" name="email" required !-- Метод 2: вложение -- label Телефон input type="tel" name="phone" /label !-- Метод 3: aria-labelledby для сложных случаев -- span id="currency-label" Сумма в /span select aria-labelledby="currency-label currency-type" option value="rub" id="currency-type" рублях /option option value="usd" долларах /option /select Сообщения об ошибках валидации должны быть программно связаны с полем и озвучиваться автоматически. Недостаточно просто показать красный текст рядом: input type="email" id="email" aria-describedby="email-error" aria-invalid="true" span id="email-error" role="alert" Введите корректный email-адрес /span Атрибут role="alert" заставляет скринридер немедленно озвучить текст при его появлении. Для менее срочных сообщений используйте role="status" (aria-live="polite") — скринридер дождётся паузы в речи. Чек-лист доступности форм: Каждый инпут имеет видимый label (не только placeholder) Placeholder не заменяет label — он исчезает при вводе Обязательные поля помечены визуально и через aria-required="true" Ошибки описывают, что не так и как исправить Группы связанных полей обёрнуты в fieldset с legend Кнопка отправки описывает действие, а не просто «Отправить» (лучше «Отправить заявку») После успешной отправки пользователь получает подтверждение, доступное скринридеру Контрастность, цвет и визуальное восприятие Около 8% мужчин и 0.5% женщин имеют то или иное нарушение цветовосприятия. Дейтеранопия (неразличение красного и зелёного) — наиболее распространённый тип. Это означает: нельзя использовать только цвет для передачи информации. Типичная ошибка — поля формы, где ошибка обозначена только красной границей. Решение: добавить иконку, текстовое описание или другой визуальный маркер помимо цвета: !-- Плохо: только цвет -- input class="input--error" !-- Красная граница -- !-- Хорошо: цвет + иконка + текст -- div class="field field--error" input aria-describedby="field-error" span class="field__error" id="field-error" svg aria-hidden="true" !-- иконка предупреждения -- /svg Заполните это поле /span /div Минимальные требования к контрастности (WCAG 2.1 AA): Обычный текст (до 18pt): 4.5:1 Крупный текст (18pt+, или 14pt+ жирный): 3:1 Элементы интерфейса (кнопки, инпуты, иконки): 3:1 Декоративные элементы, логотипы: без ограничений Инструменты для проверки контрастности: Colour Contrast Analyser (десктопное приложение), расширение Colour Contrast Checker для браузера, встроенный инструмент в DevTools Chrome (вкладка Elements → стили → цвет). Figma-плагины Contrast и Able позволяют проверять прямо в макетах. Важный нюанс: тёмная тема требует отдельной проверки контрастности. Часто дизайнеры проверяют только светлую тему, а тёмная оказывается с критическими нарушениями. Мультимедиа и альтернативный контент Изображения, видео, аудио — отдельная большая тема доступности. По данным WebAIM Million 2024, у 22% изображений на топ-1000 сайтах отсутствует атрибут alt . Это грубое нарушение, которое делает контент полностью недоступным для пользователей скринридеров. Правила написания alt-текстов: !-- Информационное изображение: описываем содержание -- img src="team.jpg" alt="Команда разработчиков 404gen на презентации проекта" !-- Декоративное изображение: пустой alt (скринридер пропустит) -- img src="divider.svg" alt="" !-- Изображение-кнопка: описываем действие -- button img src="search.svg" alt="Поиск" /button !-- Сложная инфографика: краткий alt + полное описание рядом -- figure img src="chart.png" alt="График роста конверсии" aria-describedby="chart-description" figcaption id="chart-description" Конверсия выросла с 2.1% в январе до 4.8% в декабре 2024 года благодаря редизайну страницы оформления заказа. /figcaption /figure Видеоконтент требует субтитров для пользователей с нарушениями слуха и аудиодескрипции для пользователей с нарушениями зрения. Для российских проектов: ГОСТ Р 52872-2019 требует субтитры для всего видеоконтента на государственных ресурсах. YouTube автоматически генерирует субтитры — они подходят как отправная точка, но требуют редактуры. Инструменты тестирования доступности Автоматические инструмент