Focus Management для доступности сайта: полное внедрение

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

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Focus Management для доступности сайта: полное внедрение
Средний
от 1 дня до 3 дней

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

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    980
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1240
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    994

Реализация 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 дней. Сроки зависят от архитектуры проекта и объёма существующих компонентов.

Сравнение подходов к управлению фокусом

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

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

Что входит в работу?

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

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

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