Доступность сайта: настройка WCAG и ARIA в 2026 году

Тема доступности сайтов из разряда «надо бы сделать» постепенно переходит в разряд «обязательно сделать» — и в 2026 году это уже не просто этика, это требование закона во многих странах и прямой фактор SEO-ранжирования в Google. Я разберу, как реально настроить WCAG 2.2 и ARIA-атрибуты, покажу конкретные примеры кода и расскажу, где чаще всего ошибаются разработчики.

Почему доступность сайта стала обязательной в 2026 году

Честно говоря, ещё три года назад я сам относился к теме доступности как к чему-то нишевому — мол, это для крупных государственных порталов и банков. Но ситуация изменилась кардинально. В ЕС вступил в силу European Accessibility Act (EAA), который с июня 2025 года обязывает коммерческие сайты соответствовать WCAG 2.1 уровня AA минимум. В США ADA (Americans with Disabilities Act) уже несколько лет активно применяется к веб-сайтам, и судебных исков становится всё больше. В России пока жёстких требований нет для частного бизнеса, но государственные ресурсы обязаны соответствовать ГОСТ Р 52872.

Но дело не только в юридической стороне. Google в 2024-2025 годах начал учитывать доступность как косвенный сигнал качества страницы. В Lighthouse 2026 раздел Accessibility весит отдельно, и низкий балл там напрямую влияет на общую оценку. У меня был клиент — интернет-магазин на Bitrix, PageSpeed Insights показывал 74/100, и после исправления проблем с доступностью (контраст, alt-тексты, ARIA-роли) оценка выросла до 89/100. Никаких других изменений не вносили. Подробнее о том, как работать с оценками, я писал в статье Google PageSpeed Insights 2026: улучшаем оценку сайта.

И ещё один момент — аудитория. По статистике ВОЗ, около 15% людей в мире имеют ту или иную форму инвалидности. Это не абстрактная цифра. Это ваши потенциальные покупатели, которые уходят с сайта, потому что не могут нормально им пользоваться.

WCAG 2.2: что изменилось по сравнению с 2.1

WCAG 2.2 стал официальным стандартом в октябре 2023 года, и к 2026 году это уже основной ориентир для всех. Я видел, как многие разработчики до сих пор ссылаются на 2.1 — это уже устарело. Что добавили в 2.2?

Во-первых, новый критерий 2.4.11 Focus Not Obscured (Minimum) — фокус интерактивного элемента не должен быть полностью скрыт под другими элементами (например, sticky-хедером или cookie-баннером). Это звучит очевидно, но на деле я постоянно встречаю сайты, где при навигации с клавиатуры активный элемент уходит за фиксированную шапку. Критерий уровня AA.

Во-вторых, 2.5.7 Dragging Movements — любое действие, требующее перетаскивания, должно иметь альтернативу одним нажатием. Это важно для пользователей с моторными нарушениями. В-третьих, 2.5.8 Target Size (Minimum) — минимальный размер кликабельного элемента 24×24 пикселя. Это требование уровня AA. Критерий AAA требует уже 44×44 пикселя.

Из 2.1 убрали один критерий — 4.1.1 Parsing, потому что современные браузеры научились корректно обрабатывать невалидный HTML, и этот критерий потерял практический смысл. Но это не значит, что можно писать невалидный HTML — просто формально это больше не критерий доступности.

ℹ️
Уровни соответствия WCAG: A — базовый минимум, AA — стандарт для большинства коммерческих сайтов, AAA — максимальный уровень, обычно требуется только для специализированных ресурсов. В большинстве случаев достаточно добиться уровня AA.

ARIA-атрибуты: основы и типичные ошибки

ARIA (Accessible Rich Internet Applications) — это набор атрибутов, которые добавляют семантику туда, где нативного HTML не хватает. Но тут есть золотое правило, которое я всегда повторяю коллегам: не используй ARIA там, где справляется нативный HTML. Это называется "First Rule of ARIA Use".

Грубо говоря, если можно написать <button> — пиши <button>, а не <div role="button">. Нативный элемент уже имеет все нужные роли, состояния и обработку клавиатуры. Когда ты пишешь <div role="button">, тебе ещё нужно вручную добавить tabindex="0", обработать события keydown для Enter и Space, и ты всё равно получишь худший результат. Я видел проекты, где вся кнопочная навигация была на divах — это была настоящая боль при аудите.

Но ARIA незаменима для сложных компонентов: модальных окон, выпадающих меню, аккордеонов, табов, слайдеров. Вот основные атрибуты, которые я использую регулярно:

Типичная ошибка, которую я вижу постоянно — это aria-hidden="true" на иконках внутри кнопок без текстовой метки. Правильно — скрыть иконку от скринридера и дать кнопке aria-label. Или наоборот: aria-hidden="true" на целых блоках с важным контентом, которые разработчик считал "декоративными".

Практический код: доступное модальное окно и навигация

Покажу реальный пример доступного модального окна. Это то, что я использую в своих проектах на Laravel и WordPress. Тут важно несколько вещей: управление фокусом, блокировка скролла фона, закрытие по Escape, правильные ARIA-атрибуты.

<!-- Кнопка открытия модалки -->
<button 
  type="button" 
  id="openModal" 
  aria-haspopup="dialog"
  aria-controls="contactModal">
  Открыть форму
</button>

<!-- Модальное окно -->
<div 
  id="contactModal" 
  role="dialog" 
  aria-modal="true"
  aria-labelledby="modalTitle"
  aria-describedby="modalDesc"
  class="modal"
  hidden>
  
  <div class="modal__overlay" aria-hidden="true"></div>
  
  <div class="modal__content">
    <h2 id="modalTitle">Свяжитесь с нами</h2>
    <p id="modalDesc">Заполните форму, и мы ответим в течение часа.</p>
    
    <form novalidate>
      <div class="form-group">
        <label for="userName">Ваше имя</label>
        <input 
          type="text" 
          id="userName" 
          name="name"
          autocomplete="name"
          aria-required="true"
          aria-describedby="nameError">
        <span id="nameError" role="alert" aria-live="assertive" class="error" hidden>
          Введите ваше имя
        </span>
      </div>
    </form>
    
    <button type="button" class="modal__close" aria-label="Закрыть окно">
      <svg aria-hidden="true" focusable="false">...</svg>
    </button>
  </div>
</div>

<script>
const modal = document.getElementById('contactModal');
const openBtn = document.getElementById('openModal');
const closeBtn = modal.querySelector('.modal__close');

// Запоминаем элемент, который открыл модалку
let triggerElement = null;

// Фокусируемые элементы внутри модалки
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';

function openModal() {
  triggerElement = document.activeElement;
  modal.removeAttribute('hidden');
  document.body.style.overflow = 'hidden';
  
  // Ставим фокус на первый элемент внутри модалки
  const firstFocusable = modal.querySelectorAll(focusableSelectors)[0];
  firstFocusable?.focus();
  
  // Trap focus внутри модалки
  modal.addEventListener('keydown', trapFocus);
  document.addEventListener('keydown', handleEscape);
}

function closeModal() {
  modal.setAttribute('hidden', '');
  document.body.style.overflow = '';
  modal.removeEventListener('keydown', trapFocus);
  document.removeEventListener('keydown', handleEscape);
  
  // Возвращаем фокус на элемент, открывший модалку
  triggerElement?.focus();
}

function trapFocus(e) {
  if (e.key !== 'Tab') return;
  const focusable = [...modal.querySelectorAll(focusableSelectors)];
  const first = focusable[0];
  const last = focusable[focusable.length - 1];
  
  if (e.shiftKey && document.activeElement === first) {
    e.preventDefault();
    last.focus();
  } else if (!e.shiftKey && document.activeElement === last) {
    e.preventDefault();
    first.focus();
  }
}

function handleEscape(e) {
  if (e.key === 'Escape') closeModal();
}

openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);
</script>

А вот пример доступной навигации с выпадающим меню. Это больная тема — большинство реализаций выпадашек полностью недоступны с клавиатуры:

<nav aria-label="Основная навигация">
  <ul role="list">
    <li>
      <a href="/" aria-current="page">Главная</a>
    </li>
    <li>
      <button 
        type="button"
        aria-expanded="false"
        aria-haspopup="true"
        aria-controls="servicesMenu"
        id="servicesBtn">
        Услуги
        <svg aria-hidden="true" focusable="false">
          <!-- иконка стрелки -->
        </svg>
      </button>
      <ul id="servicesMenu" role="list" hidden>
        <li><a href="/razrabotka/">Разработка</a></li>
        <li><a href="/podderzhka-bitrix/">Поддержка Bitrix</a></li>
        <li><a href="/podderzhka-wordpress/">Поддержка WordPress</a></li>
      </ul>
    </li>
  </ul>
</nav>

<script>
document.querySelectorAll('[aria-haspopup="true"]').forEach(btn => {
  const menu = document.getElementById(btn.getAttribute('aria-controls'));
  
  btn.addEventListener('click', () => {
    const isExpanded = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', !isExpanded);
    menu.hidden = isExpanded;
  });
  
  // Закрытие по Escape
  btn.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      btn.setAttribute('aria-expanded', 'false');
      menu.hidden = true;
      btn.focus();
    }
  });
});
</script>
⚠️
Частая ошибка: Не используйте display: none для скрытия элементов, которые должны быть доступны скринридерам. Используйте CSS-класс .sr-only (visually hidden) — элемент будет скрыт визуально, но доступен для вспомогательных технологий. Это критично для пропуска навигации (skip links) и скрытых меток форм.

Контраст, типографика и видимый фокус

Три вещи, которые разработчики игнорируют чаще всего — это контраст цветов, размер шрифта и видимость фокуса. Начну с контраста. WCAG 2.2 уровня AA требует минимальный коэффициент контраста 4.5:1 для обычного текста и 3:1 для крупного текста (18pt или 14pt жирный). Уровень AAA — 7:1 и 4.5:1 соответственно.

На деле я регулярно вижу серый текст на белом фоне с контрастом 2.8:1 — дизайнеры любят такие "нежные" цвета. Или белый текст на голубом фоне, где контраст едва дотягивает до 3:1. Для проверки я использую инструмент Colour Contrast Analyser или прямо в DevTools Chrome — там есть встроенная проверка контраста при наведении на элемент.

С типографикой ситуация такая: минимальный размер шрифта для основного текста — 16px. Это не требование WCAG напрямую, но это хорошая практика, и Google учитывает читаемость текста. Важно: не задавай размеры шрифтов в пикселях — используй rem. Если пользователь увеличил базовый размер шрифта в браузере до 20px, твой сайт должен масштабироваться корректно. Критерий WCAG 1.4.4 требует, чтобы текст можно было увеличить до 200% без потери контента и функциональности.

Видимый фокус — это вообще отдельная история. Я понимаю, что дизайнеры ненавидят стандартный синий outline, который браузеры рисуют по умолчанию. Но outline: none без замены — это преступление против доступности. Правильный подход — кастомный, красивый фокус-стиль. Например:

/* Убираем дефолтный стиль, но только визуально */
*:focus {
  outline: none;
}

/* Задаём красивый кастомный фокус */
*:focus-visible {
  outline: 3px solid #0056b3;
  outline-offset: 2px;
  border-radius: 2px;
}

/* Скрытый класс для вспомогательных технологий */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link для пропуска навигации */
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  background: #0056b3;
  color: white;
  padding: 8px 16px;
  z-index: 9999;
  text-decoration: none;
  font-size: 1rem;
}

.skip-link:focus {
  top: 0;
}

Псевдокласс :focus-visible — это правильный современный подход. Он показывает фокус только при навигации с клавиатуры, но не при клике мышью. Поддерживается во всех современных браузерах с 2021 года.

Доступность форм и сообщения об ошибках

Формы — это зона повышенного риска с точки зрения доступности. У меня был случай: клиент жаловался на низкую конверсию формы обратного звонка. Провели юзабилити-тест с пользователем, использующим скринридер NVDA — он вообще не мог понять, что поля обязательные, и не слышал сообщений об ошибках. Исправили — конверсия выросла на 23%.

Основные правила для форм: каждое поле должно иметь явный <label>, связанный через for и id. Placeholder — это не замена label. Никогда. Placeholder исчезает при вводе, имеет низкий контраст и не читается скринридерами как метка поля.

Сообщения об ошибках должны быть связаны с полем через aria-describedby и появляться с role="alert" или aria-live="assertive", чтобы скринридер объявил их немедленно. Поле с ошибкой должно получить aria-invalid="true". При успешной отправке формы фокус должен переместиться на сообщение об успехе — иначе пользователь с клавиатурой не поймёт, что произошло.

Ещё один момент — группировка связанных полей. Радиокнопки и чекбоксы должны быть обёрнуты в <fieldset> с <legend>. Это стандарт HTML, но его игнорируют удивительно часто.

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

Alt-тексты — это, казалось бы, базовая вещь, но по опыту вижу три типичные ошибки. Первая: alt="" у информативных изображений. Если картинка несёт смысловую нагрузку — alt должен описывать её содержание. Вторая: alt вида "картинка", "фото", "image.jpg" — это бесполезно и даже вредно, скринридер и так объявит, что это изображение. Третья: слишком длинный alt — максимум 125 символов, для сложных изображений (графиков, схем) используй aria-describedby с развёрнутым описанием в отдельном элементе.

Декоративные изображения — те, что не несут смысла (фоновые паттерны, разделители) — должны иметь пустой alt: alt="". Скринридер их пропустит. Если изображение задано через CSS background-image — оно автоматически игнорируется скринридерами, что правильно для декоративных элементов.

Для видео: субтитры обязательны (WCAG 1.2.2, уровень A). Для предзаписанного аудио — транскрипция. Автовоспроизведение со звуком — это плохая идея в любом контексте, но для людей с когнитивными нарушениями это вообще критично. Если уж делаешь автовоспроизведение — то без звука и с возможностью паузы.

💡
Совет по тестированию: Самый простой способ проверить доступность своего сайта — пройти его только с клавиатурой, без мыши. Tab для перемещения, Enter/Space для активации, Escape для закрытия. Если где-то теряется фокус или нельзя добраться до какого-то элемента — это проблема. Дополнительно запусти axe DevTools или Accessibility Insights в Chrome.

Доступность в WordPress и Bitrix: практические нюансы

На WordPress ситуация неплохая — ядро довольно доступно, Gutenberg-блоки в последних версиях (6.4, 6.5) заметно улучшились с точки зрения ARIA. Но проблемы создают темы и плагины. Большинство премиум-тем из ThemeForest — это настоящий кошмар с точки зрения доступности. Я однозначно рекомендую проверять любую тему в axe DevTools перед покупкой.

Из плагинов для повышения доступности WordPress: WP Accessibility (от Joe Dolson) — бесплатный, добавляет skip links, исправляет ряд типичных проблем. Но не думай, что плагин решит всё — он закрывает примерно 30% проблем. Остальное нужно делать руками. Если нужна серьёзная доработка сайта под стандарты доступности — это отдельная работа, которую стоит заложить в бюджет.

С Bitrix сложнее. Стандартные компоненты Bitrix имеют проблемы с доступностью — особенно каталог, корзина и форма заказа. Слайдеры на main.include.php часто не имеют никаких ARIA-атрибутов. Я в своих проектах на поддержке Bitrix обычно делаю отдельный аудит компонентов и дорабатываю шаблоны. Особенно болезненна тема с всплывающими окнами и вкладками в стандартных шаблонах — там нет ни управления фокусом, ни правильных ролей.

Для обоих CMS критично настроить язык документа: <html lang="ru">. Звучит банально, но я видел сайты без этого атрибута — скринридер читал русский текст с английским произношением, это полный провал. Проверь также наличие <main>, <header>, <nav>, <footer> с правильными ролями — это landmarks, по которым пользователи скринридеров ориентируются на странице.

Автоматическое тестирование доступности

Автоматические инструменты выявляют примерно 30-40% проблем доступности — остальное требует ручного тестирования и тестирования с реальными пользователями. Но 30-40% — это уже хорошее начало, и их можно и нужно встроить в CI/CD.

Мой стек для автоматического тестирования доступности: axe-core как движок (используется в axe DevTools, Lighthouse, Deque) и pa11y для CLI и интеграции в пайплайн. Вот простой пример настройки pa11y в GitHub Actions:

# .github/workflows/accessibility.yml
name: Accessibility Tests

on: [push, pull_request]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          
      - name: Install pa11y-ci
        run: npm install -g pa11y-ci
        
      - name: Run accessibility tests
        run: pa11y-ci --config .pa11yci.json
        
# .pa11yci.json
# {
#   "defaults": {
#     "standard": "WCAG2AA",
#     "timeout": 30000,
#     "wait": 1000
#   },
#   "urls": [
#     "https://staging.yourdomain.ru/",
#     "https://staging.yourdomain.ru/catalog/",
#     "https://staging.yourdomain.ru/contacts/"
#   ]
# }

Кроме pa11y, я использую Playwright с axe-core для более сложных сценариев — когда нужно проверить доступность после взаимодействия с интерфейсом (открытие модалки, заполнение формы). Это уже интеграционное тестирование. О том, как выстроить автоматическое тестирование в целом, хорошо написано в статье Автоматическое тестирование сайта: зачем и как.

Из браузерных расширений: axe DevTools (бесплатная версия покрывает большинство проверок), WAVE от WebAIM, Accessibility Insights от Microsoft. Последний особенно хорош для ручного тестирования — там есть пошаговые руководства по проверке конкретных критериев. И не забывай про Lighthouse — раздел Accessibility, балл ниже 90 уже повод задуматься. Про работу с Lighthouse подробнее можно почитать в статье Lighthouse 2026: аудит и улучшение скорости сайта.

Что делать прямо сейчас: приоритетный чек-лист

По опыту, полный аудит доступности среднего сайта — это 20-40 часов работы. Но если хочешь начать с самого важного, вот что даст максимальный эффект при минимальных затратах:

  1. Добавь skip link — первый элемент на странице, ссылка "Перейти к содержимому", которая появляется при фокусе. Это 10 минут работы и огромная помощь для пользователей с клавиатурой.
  2. Проверь контраст — прогони сайт через WAVE или axe, исправь все ошибки контраста. Обычно это правка нескольких CSS-переменных.
  3. Добавь alt-тексты — все информативные изображения должны иметь осмысленный alt. В WordPress это легко сделать через медиабиблиотеку, в Bitrix — через свойства файла.
  4. Исправь формы — добавь явные label ко всем полям, свяжи сообщения об ошибках через aria-describedby.
  5. Не скрывай фокус — убери outline: none без замены, добавь :focus-visible стили.
  6. Проверь структуру заголовков — h1 должен быть один на странице, заголовки должны идти иерархично (h1 → h2 → h3), без пропусков уровней.
  7. Добавь lang на html<html lang="ru">, и для многоязычных сайтов — lang на отдельных блоках с другим языком.

Это не полный список, но если сделать хотя бы эти семь пунктов — сайт станет заметно доступнее. Полный аудит и доработку я предлагаю в рамках поддержки WordPress и поддержки других CMS — это отдельная услуга с отчётом по WCAG 2.2 и планом исправлений.

Доступность — это не разовая задача. Это культура разработки. Каждый новый компонент, каждый новый шаблон нужно проверять на доступность сразу, а не потом. Технический долг в этой области накапливается быстро, и его потом дорого исправлять. Про технический долг в целом у меня есть отдельная статья — Технический долг сайта: что это и как бороться.

Хотите сделать ваш сайт доступным для всех пользователей?

Закажите аудит доступности сайта — мы приведём его в соответствие стандартам WCAG 2.2 и ARIA и расширим вашу аудиторию.

П
Павел
Веб-разработчик · 10+ лет опыта · Bitrix, WordPress, Laravel

Читайте также

Настройка rate limiting для сайта: защита от DDoS 2026 Защита XML-RPC и wp-login.php: полное руководство 2026 Защита wp-admin: закрываем доступ к панели WordPress