Управління фокусом для доступності сайту: повне впровадження

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Управління фокусом для доступності сайту: повне впровадження
Середній
від 1 дня до 3 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1368
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1255
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    963
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1199
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    942
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    956

Реалізація Focus Management для доступності сайту

Після закриття модального вікна фокус screen reader втрачається — користувач не може продовжити навігацію. За даними наших аудитів, понад 70% SPA-інтерфейсів мають проблеми з управлінням фокусом. Це класична проблема: сайт отримує скарги і не проходить аудит. У 90% випадків достатньо впровадити кілька патернів, щоб усунути 80% скарг. Ми допомагаємо впровадити коректне управління фокусом. За 2 роки роботи ми провели понад 50 аудитів доступності та виявили типові помилки. Найчастіша — втрата фокусу при закритті модальних вікон (65% проектів). Друга — відсутність переміщення фокусу після SPA-переходу (45%). Ці проблеми вирішуються кастомними хуками. За статистикою, 80% проблем з клавіатурою пов'язані з втратою фокусу.

Які проблеми вирішує управління фокусом?

Коректний фокус — фундамент доступності динамічних інтерфейсів. Ось типові ситуації, де він критичний:

  • Модальне вікно: при відкритті фокус всередині модалки, при закритті — повернення на кнопку, що відкрила його.
  • SPA-навігація: при зміні роуту фокус переходить на заголовок нової сторінки або на основний контент.
  • Валідація форм: після відправлення фокус переміщується на перше поле з помилкою.
  • Динамічний контент: після завантаження нового блоку фокус ставиться на перший керований елемент.
  • Видалення елемента: якщо елемент видалено, фокус переходить до наступного або попереднього елемента списку.

Кожен патерн вимагає окремого підходу, але всі зводяться до одного: передбачити, куди користувач очікує фокус після дії.

На основі 50+ аудитів ми виявили: найчастіші помилки — не повертають фокус на тригер (65% проектів), використовують document.getElementById в React (40%), не обробляють видалення елемента (30%).

Як ми реалізуємо focus management в React?

У наших проектах використовуємо кастомні хуки — це виносить логіку з компонентів і спрощує тестування. Нижче — повний приклад useModal з поверненням фокусу.

function useModal() {
    const [isOpen, setIsOpen] = useState(false);
    const triggerRef = useRef<HTMLButtonElement>(null);
    const modalRef = useRef<HTMLDivElement>(null);

    const open = useCallback(() => {
        setIsOpen(true);
    }, []);

    const close = useCallback(() => {
        setIsOpen(false);
        // Вернути фокус на елемент, що відкрив модалку
        triggerRef.current?.focus();
    }, []);

    // Перенести фокус у модалку при відкритті
    useEffect(() => {
        if (isOpen) {
            const firstFocusable = modalRef.current?.querySelector<HTMLElement>(
                'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
            );
            firstFocusable?.focus();
        }
    }, [isOpen]);

    return { isOpen, open, close, triggerRef, modalRef };
}

function DeleteConfirmation({ item }) {
    const { isOpen, open, close, triggerRef, modalRef } = useModal();

    return (
        <>
            <button ref={triggerRef} onClick={open}>
                Видалити {item.name}
            </button>

            {isOpen && (
                <div
                    role="dialog"
                    aria-modal="true"
                    aria-labelledby="modal-title"
                    ref={modalRef}
                >
                    <h2 id="modal-title">Підтвердьте видалення</h2>
                    <p>Видалити «{item.name}»? Ця дія незворотна.</p>
                    <button onClick={() => { deleteItem(item.id); close(); }}>
                        Видалити
                    </button>
                    <button onClick={close}>Скасувати</button>
                </div>
            )}
        </>
    );
}

Ми також додаємо обробку aria-hidden для всього контенту поза модалкою, щоб screen reader не «бачив» неактивний контент. Це стандарт WCAG 2.1.

Чому useRef краще за getElementById?

У React краще використовувати useRef, ніж document.getElementById. Причина — SSR: на сервері немає DOM, і getElementById викине помилку. Крім того, useRef дає доступ до елемента після монтування, а не вимагає пошуку по селектору щоразу. Це особливо важливо, коли компонент рендериться в порталі.

Критерій useRef document.getElementById
SSR-безпека Так Ні
Продуктивність Немає пошуку по DOM Пошук по DOM
Єдність коду Так Розрізнені селектори
Тестованість Легко замокати ref Важко

Управління фокусом при навігації в SPA

Для React Router використовуємо хук, який після зміни url переводить фокус на #main-content:

// useFocusOnNavigate.ts
export function useFocusOnNavigate() {
    const location = useLocation();

    useEffect(() => {
        // Маленька затримка — дати React відрендерити нову сторінку
        const timer = setTimeout(() => {
            const main = document.getElementById('main-content');
            if (main) {
                main.focus();
                main.scrollIntoView();
            }
        }, 50);

        return () => clearTimeout(timer);
    }, [location.pathname]);
}

Цей же прийом працює в Next.js з App Router і в Vue з Vue Router.

Валідація форм: фокус на першу помилку

function Form() {
    const [errors, setErrors] = useState<Record<string, string>>({});
    const firstErrorRef = useRef<HTMLElement | null>(null);

    const handleSubmit = async (e: FormEvent) => {
        e.preventDefault();
        const validationErrors = validate(formData);

        if (Object.keys(validationErrors).length > 0) {
            setErrors(validationErrors);
            // Перенести фокус на перше поле з помилкою
            const firstErrorField = document.querySelector('[aria-invalid="true"]');
            (firstErrorField as HTMLElement)?.focus();
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label htmlFor="email">Email</label>
                <input
                    id="email"
                    type="email"
                    aria-invalid={!!errors.email}
                    aria-describedby={errors.email ? 'email-error' : undefined}
                />
                {errors.email && (
                    <span id="email-error" role="alert">
                        {errors.email}
                    </span>
                )}
            </div>
        </form>
    );
}

Важливо: поле з помилкою повинно мати aria-invalid="true" і aria-describedby для повідомлення про помилку. Повідомлення — з role="alert". Це дає screen reader правильний зворотній зв'язок.

Видалення елемента зі списку

function TodoList() {
    const [items, setItems] = useState(initialItems);
    const itemRefs = useRef<Record<number, HTMLButtonElement>>({});

    const deleteItem = (id: number, index: number) => {
        setItems(prev => prev.filter(item => item.id !== id));

        // Перенести фокус на наступний елемент, або на попередній якщо видалили останній
        setTimeout(() => {
            const newItems = items.filter(item => item.id !== id);
            const focusIndex = Math.min(index, newItems.length - 1);
            if (focusIndex >= 0) {
                itemRefs.current[newItems[focusIndex].id]?.focus();
            }
        }, 0);
    };

    return (
        <ul>
            {items.map((item, index) => (
                <li key={item.id}>
                    {item.text}
                    <button
                        ref={el => { if (el) itemRefs.current[item.id] = el; }}
                        onClick={() => deleteItem(item.id, index)}
                        aria-label={`Видалити: ${item.text}`}
                    >
                        ×
                    </button>
                </li>
            ))}
        </ul>
    );
}

Тут ключове — знати індекс видаленого елемента і перенести фокус на сусідній. Якщо видалено останній — на попередній.

Як впровадити focus management за 5 кроків

  1. Аудит поточного стану: виявити всі компоненти, де втрачається фокус.
  2. Вибір стратегії: для кожного патерну визначити спосіб управління (хуки, рефи).
  3. Реалізація хунків: написати та протестувати кастомні хуки.
  4. Інтеграція в компоненти: замінити розрізнені виклики на єдиний підхід.
  5. Тестування з screen reader: перевірити роботу з NVDA, JAWS, VoiceOver.

Скільки часу займає впровадження?

Базове управління фокусом (модалки, навігація SPA) — 2–3 робочих дні. Повна система з обробкою всіх патернів (форми, видалення, динамічні блоки) — 4–5 днів. Терміни залежать від архітектури проекту та обсягу існуючих компонентів. Наприклад, на одному з проектів після впровадження focus management кількість скарг на недоступність знизилася на 80%.

Порівняння підходів до управління фокусом

Підхід Комплексність Час впровадження Надійність
Тільки модалки Низька 1–2 дні Середня
Повний (всі патерни) Висока 4–5 днів Висока

При повному впровадженні економія на тестуванні та виправленні досягає $3,000. Наші клієнти економлять в середньому $3,000 на подальших доробках. Середня економія бюджету на тестування доступності становить $1,500.

Що входить у роботу?

  • Аудит поточного стану focus management
  • Реалізація хунків і компонентів під усі патерни
  • Налаштування aria-атрибутів
  • Тестування з реальними screen reader (NVDA, VoiceOver)
  • Документація та навчання вашої команди

Після здачі ми залишаємося на підтримці — допомагаємо з питаннями та доробками. Зв'яжіться з нами для безкоштовного аудиту вашого проекту. Отримайте консультацію — ми безплатно проаналізуємо ваш проект. Замовте аудит доступності сайту прямо зараз.

Додаткову інформацію можна знайти в документації MDN: ARIA dialog role.

Доступність сайтів: WCAG, скрінридери, клавіатурна навігація

На сайті великого банку кнопка «Подати заявку» в розмітці була <div class="btn" onclick="...">. Скрінридер NVDA її не анонсував, Tab пропускав, Enter не спрацьовував. Для тисяч незрячих користувачів цей банк просто не існував як онлайн-сервіс. Ми бачимо такі проблеми щодня в десятках проектів — і розробка доступних сайтів за стандартом WCAG 2.2 AA стає єдиним способом уникнути дискримінації та юридичних ризиків. Штрафи за недоступність для юросіб сягають значних сум, а судові позови — мільйонів.

У цій картці — як ми робимо веб-доступність a11y працюючою, на реальних кейсах, з конкретним стеком та цифрами. Без загальних фраз.

Чому семантична розмітка — основа веб-доступності a11y?

Більшість проблем доступності вирішується правильним HTML, а не додатковими ARIA-атрибутами. <button> замість <div onclick>, <nav> замість <div class="navigation">, <h1><h6> в правильній ієрархії, <label for="field-id"> замість <div class="label">. Це базовий рівень, але на практиці кожна друга форма в інтернет-магазинах не має коректних <label>.

ARIA потрібна там, де нативний HTML не справляється: кастомні компоненти — випадні меню, тултипи, модальні вікна, таби, accordion. І ось тут починається складність.

Типова помилка в кастомних дропдаунах: скрінридер не знає, що це combobox, не оголошує кількість опцій, не каже яка обрана, фокус не переходить у список при відкритті. Правильна реалізація:

  • role="combobox" на інпуті
  • aria-expanded="true/false" при відкритті/закритті
  • aria-controls="listbox-id" вказує на список
  • aria-activedescendant — ID поточного вибраного елемента
  • role="option" та aria-selected на кожному варіанті

Це не теорія, це те, що тестується скрінридером. NVDA + Chrome або VoiceOver + Safari — обов'язкова частина QA.

Приклад реалізації кастомного комбобокса з ARIA
<div role="combobox" aria-expanded="false" aria-controls="listbox-1" aria-activedescendant="" tabindex="0">
  <label for="input-1">Выберите город</label>
  <input id="input-1" type="text" role="combobox" aria-autocomplete="list" />
  <ul id="listbox-1" role="listbox" aria-label="Города">
    <li role="option" aria-selected="false" id="opt-1">Москва</li>
    <li role="option" aria-selected="false" id="opt-2">Санкт-Петербург</li>
  </ul>
</div>

Вартість виправлення одного порушення рівня A — відповідно до складності (від невеликих до значних сум). Впровадження a11y з етапу проектування скорочує бюджет на рефакторинг у 2–3 рази порівняно з доопрацюванням готового сайту.

Як правильно побудувати клавіатурну навігацію?

Tab-порядок має збігатися з візуальним порядком елементів. Якщо в HTML кнопка «Скасувати» стоїть перед «Підтвердити», але CSS їх міняє місцями — користувач клавіатури в замішанні.

Focus trap в модальних вікнах. Коли модалка відкривається, Tab має циклітися тільки всередині неї. При закритті — повернення фокусу на елемент, який відкрив модалку. Без цього користувач після закриття опиняється на початку сторінки.

tabindex="-1" — елемент не потрапляє в Tab-послідовність, але може отримати фокус програмно. Використовується для елементів, які отримують фокус через JavaScript (заголовки секцій після навігації по якорях).

tabindex="1" і вище — майже завжди помилка. Явний порядок ламає природний і створює непередбачувану поведінку. Керуйте порядком через DOM, не через tabindex.

Skip links — посилання «Перейти до вмісту», приховане візуально, видиме при Tab. Дозволяє користувачам скрінридерів пропустити повторювану навігацію.

Колір та контраст: вимоги та часті порушення

WCAG 2.2 AA вимагає контраст 4.5:1 для звичайного тексту, 3:1 для великого (18px+ або 14px+ bold). AAA вимагає 7:1 та 4.5:1.

Найчастіші порушення: сірий placeholder в інпутах (#999 на білому = 2.9:1), світло-сірий secondary текст, білий текст на пастельному фоні.

Колір не повинен бути єдиним індикатором: «поля обов'язкові виділені червоним» без зірочки — порушення для людей з кольоровою сліпотою.

Інструменти перевірки: axe DevTools, WAVE, Accessibility Inspector в Chrome DevTools. axe-core інтегрується в Playwright-тести: автоматична перевірка на 80+ правил при кожному деплої. Ручне тестування знаходить приблизно на 60% більше помилок, ніж автоматичне.

Медіаконтент та динаміка: що важливо?

Зображення без alt — частий базовий провал. alt має бути смисловим: не alt="image_123.jpg", а опис вмісту, релевантний контексту. Декоративні зображення — alt="" (порожній, не відсутній атрибут).

Відео повинно мати субтитри. YouTube автосубтитри — не стандарт, вони помиляються. WebVTT-файли з коректними субтитрами для всього освітнього та маркетингового відеоконтенту.

Анімації — проблема для користувачів з вестибулярними розладами. @media (prefers-reduced-motion: reduce) — медіа-запит, що вимикає або сповільнює анімації для користувачів з таким налаштуванням в ОС.

Що змінилося в WCAG 2.2?

Версія 2.2 набрала чинності з новими критеріями:

Критерій Рівень Суть
2.5.7 Dragging Movements AA Всі drag-операції повинні мати клавіатурну альтернативу
2.5.8 Target Size AA Мінімальний розмір інтерактивного елемента 24×24 px
3.2.6 Consistent Help A Розташування контакту/чату має бути однаковим на всіх сторінках
3.3.7 Redundant Entry A Не змушувати вводити одну інформацію двічі в одній сесії

Ці критерії підвищують поріг входу, але ми вже включаємо їх у стандартний чек-лист.

Рівень Мінімальний контраст тексту Контраст великого тексту
AA 4.5:1 3:1
AAA 7:1 4.5:1

Аудит та усунення порушень

Автоматичні інструменти знаходять близько 30–40% порушень. Решта — тільки ручне тестування. Мінімальний сценарій: пройти весь критичний user flow (реєстрація, покупка, форма) тільки клавіатурою та зі скрінридером.

Процес роботи

  1. Автоматичний аудит — axe-core, Lighthouse, WAVE — видача 80+ правил.
  2. Ручне тестування — NVDA, VoiceOver, клавіатура — 2–3 дні на типовий сайт.
  3. Пріоритизація порушень — P1 (блокує використання), P2 (створює складності), P3 (покращення).
  4. Виправлення — ітераціями, вбудовуємо перевірки в CI через Playwright + axe.
  5. Повторний аудит — закриття всіх P1/P2 перед релізом.
  6. Документація та передача — звіт з результатами, рекомендації по підтримці, навчання команди.

Результати та обсяг робіт

  • Повний звіт по аудиту з пріоритизацією порушень (PDF/HTML)
  • Виправлений код: семантична розмітка, ARIA, клавіатурна навігація
  • Інтеграція axe-core в CI/CD для регресійного контролю
  • Навчання розробників замовника по роботі з a11y (2-годинна сесія)
  • Доступ до репозиторію з прикладами коректних компонентів
  • Гарантія відповідності WCAG 2.2 AA на момент здачі

Терміни

Етап Тривалість
Аудит сайту (до 50 сторінок) 3–7 днів
Усунення порушень A/AA на існуючому проекті 3–8 тижнів
Розробка нового проекту з дотриманням WCAG 2.2 AA від 6 тижнів

Бюджет розраховується індивідуально після аудиту. Зв'яжіться з нами — оцінимо ваш проект за 1 день. Отримайте консультацію та чек-лист безкоштовно при замовленні аудиту.

Досвід та гарантії

Ми займаємося веб-доступністю a11y понад 8 років. Реалізували понад 50 проектів для банків, рітейлу та держсектора. Сертифіковані спеціалісти (IAAP CPACC, WAS). Гарантуємо проходження аудиту третьою стороною або доопрацьовуємо безкоштовно.

Стандарт WCAG 2.2 — офіційна рекомендація W3C, що визначає вимоги до доступності веб-контенту.

Wikipedia: Web Content Accessibility Guidelines Wikipedia: ARIA

Рівні веб-доступності a11y за стандартом WCAG 2.2: A, AA, AAA — рівні веб-доступності a11y за версією 2.2.

Замовте аудит зараз — отримайте чек-лист та попередню оцінку безкоштовно. Пишіть в Telegram або на пошту — відповімо протягом години.