Доступность: сайт для всех

Доступность (A11y): семантика, клавиатурная навигация, видимый фокус, ARIA, контраст и анимации для пользователей с разными возможностями.

Уровень: средний 12 разделов 2 часа

1. Что такое доступность

Доступность (англ. accessibility, сокращённо a11y — «11 букв между a и y») — это возможность использовать сайт всем людям, независимо от ограничений: незрячим и слабовидящим, людям с нарушениями моторики, слуха, а также пожилым и тем, кто просто пользуется сайтом с клавиатуры, телефона или при плохом сетевом соединении.

Доступность выгодна не только людям с инвалидностью: она улучшает сайт для всех. Хорошая контрастность помогает при ярком солнце, понятная клавиатурная навигация — когда нет мыши, а осмысленная семантика — поисковикам и голосовым ассистентам.

Универсальный принцип: если на сайт можно попасть только мышью и глазами — это барьер. Пробуйте периодически управлять своим сайтом с клавиатуры и со скринридером.

2. Семантика — скелет доступности

Семантические теги дают скринридеру и браузеру понять структуру страницы: где навигация, где основное содержание, что является кнопкой, а что просто текст. Невидимые программы не смотрят на страницу — они читают HTML.

<div class="header">        <span class="btn">Главная</span>   <!-- нельзя -->
<header>              <button>Главная</button>  <!-- правильно -->

Базовый скелет доступной страницы использует теги header, nav, main, aside, footer и один элемент main на страницу. Скринридер по этим тегам предлагает пользователю быструю навигацию: «перейти к содержанию», «перейти к навигации».

Использовать div с обработчиком клика вместо button — самая частая ошибка: скринридер не знает, что элемент нажимается, а клавиатура не может нажать Enter. Если нужен нестандартный внешний вид кнопки — стилизуйте настоящий button.

3. Заголовки и порядок чтения

Заголовки — это содержание страницы для скринридера. Пользователь открывает список заголовков и перемещается по ним, как по оглавлению. Поэтому важна не только семантика, но и осмысленная иерархия: h1 один на страницу, дальше уровни не прыгают через один.

<h1>Каталог</h1>          <!-- единственный h1 -->
<h2>Ноутбуки</h2>
  <h3>Игровые</h3>
  <h3>Для работы</h3>
<h2>Мониторы</h2>
Порядок чтения идёт по порядку в DOM, а не по визуальному расположению. Элемент, который CSS-ом передвинут вправо, скринридер прочитает там, где он находится в HTML. Правьте DOM, а не только стили, если структура читается нелогично.

4. Ссылки и кнопки

Имя ссылки и кнопки — это её текст или aria-label. Имя должно быть осмысленным отдельно от контекста: «подробнее» и «читать дальше» повторены сто раз на странице — бесполезные для пользователя скринридера ориентиры, ему приходится открывать каждую, чтобы понять, куда ведёт.

<a href="catalog/iphone-15.html">Подробнее об iPhone 15</a>
<a href="catalog/airpods.html">Подробнее об AirPods</a>

<!-- вместо: <a href="...">Подробнее</a> и <a href="...">Подробнее</a> -->

5. Клавиатура и фокус

Каждый интерактивный элемент должен получать фокус с клавиатуры (Tab), а действия выполняться по Enter/Space. Нативные элементы этой логике уже следуют — ещё один довод не изобретать кнопки из div.

a:focus, button:focus, input:focus {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
}

/* не надо:  outline: none;  без замены на видимый индикатор */
Проверка одной кнопкой: откройте страницу и пройдитесь только клавишами Tab, Enter, Space. Доступен весь интерфейс? Понятно, где вы сейчас находитесь? Если нет — видимый фокус и порядок элементов требуют исправлений.

6. Скринридер: скрытие и описания

Иногда элемент нужен визуально, но не нужен скринридеру (декоративная иконка), а иногда — наоборот: текст существует только для скринридера (подпись «Пропустить меню»). Двум ситуациям отвечают aria-hidden и класс sr-only.

<!-- декоративную иконку скрываем от скринридера -->
<button>
  <svg aria-hidden="true" class="icon">…</svg>
  Удалить
</button>

/* текст, видимый только скринридеру */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  clip-path: inset(50%);
  margin: -1px;
  overflow: hidden;
}
Не вешайте aria-hidden="true" на фокусируемый элемент: невидимый для скринридера фокус переводит пользователя «в никуда». aria-hidden допустимо только на недекоративное статичное содержимое.

7. ARIA: роль, имя, состояние

ARIA-атрибуты исправляют то, что не выражается нативными тегами: роль элемента, его имя и текущее состояние. Главное правило: без ARIA, если можно нативным тегом — нативные элементы уже поддерживаются скринридерами одинаково хорошо.

<!-- состояние раскрывающегося меню -->
<button aria-expanded="false" aria-controls="menu">Разделы</button>
<ul id="menu" hidden>…</ul>

<!-- роль с именем для svg-диаграммы -->
<svg role="img" aria-label="Продажи в 2025 году: 120 млн руб.">…</svg>
ARIA-атрибуты не меняют поведение — только описывают его. Раскрытие меню и движение фокуса всё равно делают JS, а aria лишь рассказывает об этом скринридеру. ARIA не «чинит», а «рассказывает».

8. Формы: подписи и ошибки

Каждое поле должно иметь текстовую подпись, связанную с ним: скринридер объявит «Имя: редактируемое поле» при фокусе. Связь делается через label for и id, либо обёрткой поля в label.

<label for="email">Электронная почта</label>
<input type="email" id="email" name="email" required>

<label for="password">Пароль</label>
<input type="password" id="password" name="password"
       autocomplete="current-password">

Сообщение об ошибке должно быть не только цветным: цвет не справится для незрячего и дальтоника. Свяжите текст ошибки с полем через aria-describedby, а невалидное состояние обозначьте aria-invalid.

<label for="phone">Телефон</label>
<input type="tel" id="phone" name="phone"
       aria-describedby="phone-err" aria-invalid="true">
<span id="phone-err" class="error">Формат: +7 900 000-00-00</span>

9. Контраст и размер текста

Текст должен читаться при любых условиях. WCAG 2.1 задаёт два порога: не менее 4.5:1 для обычного текста и 3:1 для крупного (от 24px или 18.66px жирного). На практике берите 4.5:1 как рабочее правило и проверяйте светлые цвета на фоне.

КонтрастГде применяется
4.5:1 и вышевесь обычный текст и интерфейс
3:1 и вышекрупный текст (24px+), интерфейсы и объекты
1.4.4 текстразмер не фиксируется, страница читается при увеличении до 200%
/* серый текст на белом: проверьте, а не красивый ли он у вас случайно */
body { color: #1f2937; }                /* #1f2937 на #ffffff = ~16:1  */
.muted { color: #6b7280; }              /* #6b7280 на #ffffff = ~4.4:1  */
.bad   { color: #9ca3af; }              /* ~2.8:1 — уже не проходит */

10. Изображения и мультимедиа

У каждого изображения со смыслом есть альтернативный текст alt: скринридер читает его вместо картинки. Пустой alt у декоративных картинок, а не отсутствие атрибута вообще — отсутствие заставляет читать имя файла.

<!-- полезное фото: описываем содержание -->
<img src="team.jpg" alt="Команда на тимбилдинге в горах">

<!-- декоративная картинка: ничего не значит для смысла -->
<img src="decoration.svg" alt="" class="hero-decor">

<!-- iframe-видео: подпись и альтернатива -->
<iframe title="Демо: как собрать лендинг" src="https://…"></iframe>

11. Анимации и motion

Движение, параллакс и вспышки вызывают у части людей тошноту, головокружение и даже приступы. Пользователи с prefers-reduced-motion: reduce просят систему: «не двигай». Веб должен это уважать.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

12. Проверка и чек-лист

Доступность проверяется снаружи: инструменты находят грубые ошибки, а человек — часть барьеров. Работайте в цикле: автоматическая проверка → ручная с клавиатурой → тест со скринридером.

Проход Lighthouse A11y без ошибок не гарантирует доступность: автоматические проверки ловят примерно треть проблем. Остальное — клавиатура, чтение вслух, проверка реальными пользователями.

Минимальный чек-лист до публикации

Самый честный тест доступности — показать страницу человеку с ограничением. Если доступен такой вариант — используйте его; если нет, замените проходом со скринридером и клавиатурой.

Что дальше

Курс завершён.

← Вернуться к курсам