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

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

В данном разделе описаны обязательные технические требования и лучшие практики для обеспечения доступности (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>