Технические требования к доступности
В данном разделе описаны обязательные технические требования и лучшие практики для обеспечения доступности (a11y) в проекте.
Семантика и HTML
Buttons vs Links
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 (если нативный элемент невозможен)
Кастомный элемент обязан воспроизвести все поведение:
- Role — семантика для ассистивных технологий
- Tabindex — включение в tab-порядок
- Обработка Enter/Space и других клавиш — для интерактивности
- 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
- Один h1 на страницу — основной заголовок документа
- Уровни следуют строго последовательно: h1 → h2 → h3 → h4
- h2-h6 можно использовать несколько раз на странице, включая повторения одного уровня подряд
- Разрешено использовать несколько уровней секций:
- h2 → h3 → h4 и следом h2 → h3
- При условии что уровни не пропускаются и каждый заголовок следует логической структуре контента
- Заголовок отражает структуру контента, а не используется для стилизации
FORBIDDEN
- Пропуск уровней: h1 → h3
- Использовать заголовок ради визуального стиля
Примеры
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
- ARIA — дополнение к семантике, не замена
- Не использовать ARIA для исправления неправильного HTML
SHOULD
Когда ARIA атрибуты необходимы:
- Кастомные элементы без нативных аналогов (табы, комбобоксы)
- Объявления связей между элементами:
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
- Ошибки связываем с полем через
aria-describedby - Поле с ошибкой отмечаем
aria-invalid="true" - Ошибка должна быть текстом (не только цвет/иконка)
- Сообщения об ошибках и подсказки доступны скринридеру
Примечание: Если используется UI-library (например, MUI, AntDesign, Chakra) — они уже используют встроенны е механизмы для a11y.
Клавиатурная навигация
MUST
- Все интерактивные элементы доступны через Tab
- Фокус должен быть визуально заметным
- Порядок фокуса соответствует визуальному потоку элементов
Стилизация фокуса
MUST
- В проекте должны быть централизованные стили фокуса
- Фокус не должен быть подавлен (
outline: none) без замены на кастомный фокус
SHOULD
:focus-visible вместо :focus — фокус отображается только при клавиатурной навигации
✅ Хорошо button:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: 2px;
}
❌ Плохо button:focus {
outline: none; /* без замены */
}
Динамические элементы и Модалки
Модальные окна
MUST
- Focus trap — фокус не покидает модал при Tab
- При закрытии фокус возвращается на элемент, вызвавший модалку
Динамический контент
MUST
- Появление динамического элемента (например, alert):
- Фокус переносится на него
- Или используется
aria-liveдля анонса
- Удаление элемента с фокусом:
- Фокус перемещается на логически следующий элемент
- Фокус не теряется
✅ Хорошо — 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
- Каждый
<img>должен иметь атрибутalt - Содержимое
altзависит от роли изображения:- Информационное — описание содержимого
- Функциональное (внутри ссылки, кнопки) — описание действия
- Декоративное —
alt=""(атрибутaltобязателен, не удалять)