Аудит доступности (Accessibility Audit) сайта
Аудит доступности выявляет барьеры, мешающие пользователям с нарушениями зрения, слуха, моторики или когнитивными особенностями работать с сайтом. Стандарт — WCAG 2.1/2.2 уровень AA, который обязателен для государственных сайтов во многих странах и де-факто стандарт для коммерческих.
Что входит в аудит
Автоматизированное сканирование — выявляет ~30–40% нарушений: отсутствующие alt-тексты, недостаточный контрастность, неправильная структура заголовков, недостающие ARIA-атрибуты.
Ручное тестирование — обязательно для интерактивных элементов: навигация с клавиатуры, работа со скринридером, поведение модальных окон и форм.
Тестирование с реальными AT — NVDA + Firefox (Windows), VoiceOver + Safari (macOS/iOS), TalkBack (Android).
Автоматизированное сканирование
# axe-core через CLI
npm install -g @axe-core/cli
axe https://mysite.com --reporter=json > axe-report.json
# Lighthouse Accessibility
npx lighthouse https://mysite.com \
--only-categories=accessibility \
--output=html \
--output-path=./lighthouse-a11y.html
# Pa11y: batch scan всех страниц
npm install -g pa11y-ci
# .pa11yci
defaults:
standard: WCAG2AA
runners:
- axe
- htmlcs
timeout: 30000
urls:
- https://mysite.com
- https://mysite.com/about
- https://mysite.com/contact
- https://mysite.com/blog
pa11y-ci --config .pa11yci --json > pa11y-report.json
# Парсим результаты
node -e "
const r = require('./pa11y-report.json');
const issues = Object.values(r.results).flat();
const errors = issues.filter(i => i.type === 'error');
console.log('Total errors:', errors.length);
errors.slice(0,10).forEach(e => console.log(e.code, e.selector));
"
Ключевые критерии WCAG AA
| Критерий | Требование | Частая ошибка |
|---|---|---|
| 1.1.1 Alt Text | Все изображения имеют alt | Декоративные изображения без alt="" |
| 1.3.1 Info and Relationships | Структура передана семантикой | <div> вместо <button>, <nav>, <main> |
| 1.4.3 Contrast | 4.5:1 для текста, 3:1 для крупного | Серый текст на белом фоне |
| 1.4.4 Resize Text | До 200% без потери контента | Фиксированный height на блоках |
| 2.1.1 Keyboard | Всё доступно с клавиатуры | Нет фокуса на кастомных dropdown |
| 2.4.3 Focus Order | Логический порядок фокуса | tabindex с произвольными числами |
| 2.4.7 Focus Visible | Видимый индикатор фокуса | outline: none без альтернативы |
| 3.3.1 Error Identification | Ошибки форм описаны текстом | Только цвет для обозначения ошибки |
| 4.1.2 Name, Role, Value | ARIA для кастомных компонентов | Нет role/aria-label на иконках-кнопках |
Ручная проверка: чеклист
Навигация с клавиатуры:
- Tab обходит все интерактивные элементы
- Shift+Tab работает в обратном направлении
- Enter/Space активирует кнопки
- Escape закрывает модальные окна и dropdown
- Стрелки работают внутри
<select>, radio groups, tablist
Фокус-ловушки:
// Правильное управление фокусом в модальном окне
const Modal = ({ isOpen, onClose, children }) => {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen) {
// Сохраняем предыдущий элемент фокуса
const previouslyFocused = document.activeElement as HTMLElement;
// Перемещаем фокус в модальное окно
modalRef.current?.focus();
return () => {
// Возвращаем фокус при закрытии
previouslyFocused?.focus();
};
}
}, [isOpen]);
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabIndex={-1}
>
{children}
</div>
);
};
Проверка контрастности:
# Через axe DevTools в браузере — вкладка Elements
# Или programmatically:
node -e "
const { getContrastRatio } = require('polished');
const ratio = getContrastRatio('#767676', '#ffffff');
console.log('Ratio:', ratio.toFixed(2), ratio >= 4.5 ? 'PASS' : 'FAIL AA');
"
Типичные находки и исправления
Кастомные компоненты без ARIA:
// ПЛОХО: иконка-кнопка без описания
<div onClick={handleDelete}>
<TrashIcon />
</div>
// ХОРОШО:
<button
type="button"
onClick={handleDelete}
aria-label="Удалить запись"
>
<TrashIcon aria-hidden="true" />
</button>
Форма без связи label–input:
<!-- ПЛОХО -->
<label>Email</label>
<input type="email" name="email">
<!-- ХОРОШО -->
<label for="email">Email</label>
<input type="email" id="email" name="email">
<!-- Или через aria-labelledby -->
<span id="email-label">Email</span>
<input type="email" aria-labelledby="email-label">
Skip navigation:
<!-- Первый элемент страницы — ссылка для пропуска навигации -->
<a href="#main-content" class="skip-link">
Перейти к основному содержимому
</a>
<nav>...</nav>
<main id="main-content">...</main>
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 0;
top: 0;
z-index: 9999;
}
Интеграция в CI/CD
# GitHub Actions
- name: Accessibility Check
run: |
npx pa11y-ci --config .pa11yci --threshold 0
# threshold 0 = ни одной ошибки WCAG AA не пропускаем
# Или axe в Playwright тестах
- name: Run axe in E2E
run: npx playwright test accessibility.spec.ts
// accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('Homepage should not have accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
expect(results.violations).toEqual([]);
});
Формат отчёта
После аудита предоставляется структурированный отчёт:
- Критические (блокируют работу с клавиатурой или скринридером) — немедленное исправление
- Серьёзные (нарушения WCAG AA) — исправление в течение спринта
- Умеренные (рекомендации WCAG AAA, best practices) — roadmap
Аудит сайта до 20 страниц — 2–3 рабочих дня. Включая тестирование с NVDA и ручную проверку всех интерактивных сценариев.







