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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Focus Management для доступности сайта
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

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

Focus Management — управление тем, на каком элементе находится клавиатурный фокус в динамических интерфейсах. Некорректный фокус превращает SPA в непригодный для использования продукт для пользователей screen reader и клавиатурной навигации.

Когда нужно управлять фокусом

  • Открытие/закрытие модального окна
  • Навигация между страницами в SPA
  • Появление/скрытие динамического контента
  • Завершение мультишагового процесса (wizard)
  • Удаление элемента из списка
  • Уведомления об ошибках валидации

Модальное окно: полный цикл

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>
            )}
        </>
    );
}

Навигация в SPA (React Router)

// 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]);
}

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

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>
    );
}

Удаление элемента из списка

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>
    );
}

useRef vs getElementById

Предпочтительно использовать useRef вместо document.getElementById в React — это безопаснее при SSR и тестировании.

Srок реализации

Базовое управление фокусом (модалки, навигация SPA): 2–3 дня. Полная система с обработкой всех паттернов: 4–5 дней.