Реалізація підтримки VoiceOver/Screen Reader (WCAG)
Екранна читалка — програмне забезпечення, яке озвучує вміст сторінки для сліпих та слабозорих користувачів. VoiceOver (macOS/iOS), NVDA та JAWS (Windows), TalkBack (Android) оголошують вміст сторінки та забезпечують навігацію клавіатурою. WCAG 2.1 рівень AA вимагає повної доступності для екранних читалок.
Як працюють екранні читалки
Екранні читалки читають DOM-дерево та використовують Accessibility Tree — спеціальне представлення сторінки, побудоване з HTML та ARIA-атрибутів. Користувачі навігують за допомогою заголовків (H1-H6), посилань, форм та таблиць за допомогою спеціальних клавіатурних скорочень.
Базові вимоги
Семантична розмітка замість div-soup:
<!-- Погано -->
<div class="header">
<div class="nav">
<div class="nav-item" onclick="go('/home')">Домашня</div>
</div>
</div>
<!-- Добре -->
<header>
<nav aria-label="Основна навігація">
<ul>
<li><a href="/home">Домашня</a></li>
</ul>
</nav>
</header>
Орієнтири для навігації:
<header>...</header>
<nav aria-label="Основна навігація">...</nav>
<main>
<article>...</article>
<aside aria-label="Пов'язаний контент">...</aside>
</main>
<footer>...</footer>
Форми
<!-- Кожне поле форми повинно мати мітку -->
<label for="email">Email</label>
<input type="email" id="email" name="email"
aria-describedby="email-hint email-error"
aria-required="true">
<span id="email-hint" class="hint">Ми не будемо надсилати спам</span>
<span id="email-error" role="alert" aria-live="polite"></span>
<!-- Згруповані поля -->
<fieldset>
<legend>Спосіб оплати</legend>
<label><input type="radio" name="payment" value="card"> Карта</label>
<label><input type="radio" name="payment" value="cash"> Готівка</label>
</fieldset>
Динамічний вміст та SPA
Основна проблема для екранних читалок у SPA — динамічні оновлення DOM не оголошуються автоматично.
// React — оголошення завантаження даних
function DataSection({ isLoading, data }) {
return (
<section>
{/* aria-live для оголошення змін */}
<div aria-live="polite" aria-atomic="true" className="sr-only">
{isLoading ? 'Завантаження даних...' : 'Дані завантажені'}
</div>
{isLoading ? (
<div aria-busy="true">
<span className="sr-only">Завантаження...</span>
<Spinner aria-hidden="true" />
</div>
) : (
<ul>
{data.map(item => <li key={item.id}>{item.title}</li>)}
</ul>
)}
</section>
);
}
Управління фокусом під час навігації
// При переході між сторінками в SPA — переміщення фокуса на заголовок сторінки
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
function PageTitle({ title }) {
const titleRef = useRef(null);
const location = useLocation();
useEffect(() => {
titleRef.current?.focus();
document.title = title;
}, [location.pathname]);
return (
<h1 tabIndex={-1} ref={titleRef} className="focus-invisible">
{title}
</h1>
);
}
Зображення та медіа
<!-- Інформаційне зображення -->
<img src="graph.png" alt="Діаграма зростання продажів: +35% у Q3 2024">
<!-- Декоративне зображення -->
<img src="decorative-bg.png" alt="" role="presentation">
<!-- Складна діаграма — додатковий текстовий опис -->
<figure>
<img src="complex-chart.png" alt="Діаграма розподілу доходів"
aria-describedby="chart-desc">
<figcaption id="chart-desc">
Діаграма показує: 40% доходів від продукту A, 35% від продукту B, 25% інше.
</figcaption>
</figure>
Тестування екранної читалки
Інструменти:
- NVDA (Windows, безплатно) — найпоширеніша
- JAWS (Windows, комерційна) — корпоративний стандарт
- VoiceOver (macOS: Cmd+F5, iOS: потрійний клік Home)
- ChromeVox (Chrome розширення)
Автоматизоване тестування:
- axe-core — бібліотека для Playwright/Cypress
- jest-axe — для unit-тестів React компонентів
// Jest + jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form is accessible', async () => {
const { container } = render(<ContactForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Часові рамки
- Аудит поточного стану: 1–2 дні
- Виправлення семантики та ARIA на існуючому проекті: 3–7 днів
- Тестування екранної читалки + доводка: 2–3 дні







