Перейти к основному содержимому

Технические требования к доступности

В данном разделе описаны обязательные технические требования и лучшие практики для обеспечения доступности (a11y) в проекте.


Семантика и HTML

MUST

  • <button> — если действие происходит на текущей странице (submit, open modal, toggle)
  • <a href> — если это навигация/переход на URL
✅ Хорошо
<button type="button" onClick="{openModal}">Edit</button>
<a href="/settings">Go to settings</a>

❌ Плохо
<div onClick="{openModal}">Edit</div>
<span role="button" onClick="{openModal}">Edit</span>

Кастомные интерактивные элементы

FORBIDDEN

Div, span и т.п. как интерактивные элементы использоваться не должны

MUST (если нативный элемент невозможен)

Кастомный элемент обязан воспроизвести все поведение:

  1. Role — семантика для ассистивных технологий
  2. Tabindex — включение в tab-порядок
  3. Обработка Enter/Space и других клавиш — для интерактивности
  4. ARIA-состояния
❌ Плохо
<div onClick="{openModal}">Edit</div>

✅ Правильно (только если button невозможен)
<div role="button" tabindex="0" onClick="{openModal}" onKeyDown="{(e)" ="">
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openModal(); }
}} aria-label="Edit profile"> Edit
</div>

✅ Лучше всего
<button type="button" onClick="{openModal}">Edit</button>

Правило для больших проектов: не делаем кликабельные div/span. Используем button/link или готовый компонент.


Иерархия заголовков (Heading hierarchy) (H1–H6)

Описание

Заголовки формируют "каркас" страницы для скринридеров и навигации с клавиатуры. Правильная структура помогает быстро понять, что на странице и перейти к нужному разделу.

MUST

  1. Один h1 на страницу — основной заголовок документа
  2. Уровни следуют строго последовательно: h1 → h2 → h3 → h4
  3. h2-h6 можно использовать несколько раз на странице, включая повторения одного уровня подряд
  4. Разрешено использовать несколько уровней секций:
    • h2 → h3 → h4 и следом h2 → h3
    • При условии что уровни не пропускаются и каждый заголовок следует логической структуре контента
  5. Заголовок отражает структуру контента, а не используется для стилизации

FORBIDDEN

  1. Пропуск уровней: h1 → h3
  2. Использовать заголовок ради визуального стиля

Примеры

1. ✅ Хорошо
<h1>Profile</h1>

<section>
<h2>Personal information</h2>
<h3>Contact details</h3>
</section>

<section>
<h2>Security</h2>
<h3>Password</h3>
</section>

2. ❌ Плохо (пропуск уровня)
<h1>Profile</h1>
<h3>Personal information</h3>

3. ❌ Плохо (заголовок ради стиля)
<h2 class="text-xl">Buy now</h2>
<!-- это кнопка/CTA, а не заголовок секции -->

4. ✅ Правильно (стиль без заголовка)
<p class="text-xl font-semibold">Buy now</p>

Accessible name (имя элемента)

MUST

Каждый input/select/textarea должен иметь label:

  • <label htmlFor="id"> + id на input
  • Либо aria-label
  • Либо aria-labelledby
✅ Хорошо
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />

✅ Допустимо (когда визуального label нет)
<input aria-label="Search" />

❌ Плохо
<input placeholder="Email" />

ARIA

Принципы

MUST

  1. ARIA — дополнение к семантике, не замена
  2. Не использовать ARIA для исправления неправильного HTML

SHOULD

Когда ARIA атрибуты необходимы:

  1. Кастомные элементы без нативных аналогов (табы, комбобоксы)
  2. Объявления связей между элементами: aria-describedby, aria-labelledby

FORBIDDEN

Дублирование нативной семантики:

❌ Плохо — избыточно
<button role="button">Click</button>
<a role="link" href="/about">About</a>

✅ Хорошо — нативная семантика достаточна
<button>Click</button>
<a href="/about">About</a>

Forms & Validation

MUST

  1. Ошибки связываем с полем через aria-describedby
  2. Поле с ошибкой отмечаем aria-invalid="true"
  3. Ошибка должна быть текстом (не только цвет/иконка)
  4. Сообщения об ошибках и подсказки доступны скринридеру

Примечание: Если используется UI-library (например, MUI, AntDesign, Chakra) — они уже используют встроенные механизмы для a11y.


Клавиатурная навигация

MUST

  1. Все интерактивные элементы доступны через Tab
  2. Фокус должен быть визуально заметным
  3. Порядок фокуса соответствует визуальному потоку элементов

Стилизация фокуса

MUST

  1. В проекте должны быть централизованные стили фокуса
  2. Фокус не должен быть подавлен (outline: none) без замены на кастомный фокус

SHOULD

:focus-visible вместо :focus — фокус отображается только при клавиатурной навигации

✅ Хорошо button:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: 2px;
}

❌ Плохо button:focus {
outline: none; /* без замены */
}

Динамические элементы и Модалки

Модальные окна

MUST

  1. Focus trap — фокус не покидает модал при Tab
  2. При закрытии фокус возвращается на элемент, вызвавший модалку

Динамический контент

MUST

  1. Появление динамического элемента (например, alert):
    • Фокус переносится на него
    • Или используется aria-live для анонса
  2. Удаление элемента с фокусом:
    • Фокус перемещается на логически следующий элемент
    • Фокус не теряется
✅ Хорошо — focus trap в модалке (Headless UI)
import { Dialog } from '@headlessui/react';

<Dialog open={isOpen} onClose={closeModal}>
<Dialog.Panel>
{/* Фокус автоматически trapped */}
<button onClick={closeModal}>Close</button>
</Dialog.Panel>
</Dialog>

✅ Хорошо — ручной возврат фокуса
const buttonRef = useRef<HTMLButtonElement>(null);

const openModal = () => {
setIsOpen(true);
};

const closeModal = () => {
setIsOpen(false);
buttonRef.current?.focus(); // возврат фокуса
};

Изображения и альтернативный текст

MUST

  1. Каждый <img> должен иметь атрибут alt
  2. Содержимое alt зависит от роли изображения:
    • Информационное — описание содержимого
    • Функциональное (внутри ссылки, кнопки) — описание действия
    • Декоративноеalt="" (атрибут alt обязателен, не удалять)

Требования к тексту

MUST

  1. Описывает смысл, не внешний вид
  2. Не дублирует текст рядом — если под изображением есть подпись, alt должен дополнять, не повторять
  3. Текст должен быть лаконичным — 1-2 предложения максимум

Псевдоэлементы

MUST

  1. ::before и ::after невидимы для скринридеров — контент в них не попадает в accessibility tree
  2. Информация для пользователя должна быть в DOM, не в CSS-псевдоэлементах

MAY

Псевдоэлементы допустимы для декоративных целей

❌ Плохо — важная информация в псевдоэлементе .required::after {
content: ' (required)';
color: red;
}


Хорошо

информация
в
DOM
<label
> Email
<span
aria-label='required'
> *</span
> </label
>
Хорошо

декоративное
использование
.card::before {
content: '';
position: absolute;
background: linear-gradient(...);
}

UI-библиотеки

MAY

Допустимо использовать UI-библиотеки с поддержкой доступности из коробки:

  • MUI (Material UI) — styled-компоненты
  • Radix UI — headless решение, полный контроль над стилями

MUST

  1. Библиотека не снимает ответственности за a11y
  2. При стилизации и расширении компонентов библиотеки сохраняются:
    • Поведение фокуса
    • ARIA-атрибуты и роли
    • Клавиатурные взаимодействия

FORBIDDEN

Расширять/стилизовать компоненты так, что они теряют доступность

❌ Плохо — сломали фокус
<Button
sx={{
'&:focus': { outline: 'none' } // удалили outline без замены
}}
>
Click me
</Button>

✅ Хорошо — сохранили a11y
<Button
sx={{
'&:focus-visible': {
outline: '2px solid',
outlineColor: 'primary.main',
outlineOffset: 2
}
}}
>
Click me
</Button>