Тема доступности сайтов из разряда «надо бы сделать» постепенно переходит в разряд «обязательно сделать» — и в 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 — просто формально это больше не критерий доступности.
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 незаменима для сложных компонентов: модальных окон, выпадающих меню, аккордеонов, табов, слайдеров. Вот основные атрибуты, которые я использую регулярно:
role— определяет роль элемента (dialog, navigation, alert, tab, tabpanel и др.)aria-label— текстовая метка для элемента, когда визуальный текст отсутствует или недостаточенaria-labelledby— ссылка на ID элемента, который является меткойaria-describedby— ссылка на ID элемента с описаниемaria-expanded— состояние раскрытого/свёрнутого элементаaria-hidden="true"— скрыть элемент от скринридеровaria-live— для динамически обновляемого контентаaria-current— текущий элемент в навигации, пагинации, шагах
Типичная ошибка, которую я вижу постоянно — это 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). Для предзаписанного аудио — транскрипция. Автовоспроизведение со звуком — это плохая идея в любом контексте, но для людей с когнитивными нарушениями это вообще критично. Если уж делаешь автовоспроизведение — то без звука и с возможностью паузы.
Доступность в 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 часов работы. Но если хочешь начать с самого важного, вот что даст максимальный эффект при минимальных затратах:
- Добавь skip link — первый элемент на странице, ссылка "Перейти к содержимому", которая появляется при фокусе. Это 10 минут работы и огромная помощь для пользователей с клавиатурой.
- Проверь контраст — прогони сайт через WAVE или axe, исправь все ошибки контраста. Обычно это правка нескольких CSS-переменных.
- Добавь alt-тексты — все информативные изображения должны иметь осмысленный alt. В WordPress это легко сделать через медиабиблиотеку, в Bitrix — через свойства файла.
- Исправь формы — добавь явные label ко всем полям, свяжи сообщения об ошибках через aria-describedby.
- Не скрывай фокус — убери
outline: noneбез замены, добавь:focus-visibleстили. - Проверь структуру заголовков — h1 должен быть один на странице, заголовки должны идти иерархично (h1 → h2 → h3), без пропусков уровней.
- Добавь lang на html —
<html lang="ru">, и для многоязычных сайтов — lang на отдельных блоках с другим языком.
Это не полный список, но если сделать хотя бы эти семь пунктов — сайт станет заметно доступнее. Полный аудит и доработку я предлагаю в рамках поддержки WordPress и поддержки других CMS — это отдельная услуга с отчётом по WCAG 2.2 и планом исправлений.
Доступность — это не разовая задача. Это культура разработки. Каждый новый компонент, каждый новый шаблон нужно проверять на доступность сразу, а не потом. Технический долг в этой области накапливается быстро, и его потом дорого исправлять. Про технический долг в целом у меня есть отдельная статья — Технический долг сайта: что это и как бороться.
Хотите сделать ваш сайт доступным для всех пользователей?
Закажите аудит доступности сайта — мы приведём его в соответствие стандартам WCAG 2.2 и ARIA и расширим вашу аудиторию.