React и ally.js

Для работы с ally.js в проекте на React необходимо сначала установить библиотеку через npm или yarn:

npm install ally.js
# или
yarn add ally.js

После установки можно импортировать необходимые функции из библиотеки:

import ally from 'ally.js';

ally.js не зависит от конкретного фреймворка, что делает её удобной для использования в React-компонентах. Однако важно помнить о синхронизации с жизненным циклом компонентов, чтобы не оставлять активные обработчики после размонтирования.

Основные концепции ally.js

ally.js ориентирована на улучшение доступности веб-приложений, предоставляя инструменты для работы с фокусом, выделением элементов и проверкой доступности. Основные направления:

  • Управление фокусом – нахождение и установка фокуса на интерактивные элементы.
  • Обход по доступным элементам – фильтрация элементов с учётом их видимости и доступности.
  • Проверка доступности элементов – анализ элементов на предмет ARIA-атрибутов и корректного использования интерактивных компонентов.

Навигация и поиск фокуса

Поиск следующего или предыдущего фокусируемого элемента

ally.js предоставляет метод nextTabbable и previousTabbable для поиска следующего или предыдущего интерактивного элемента в DOM:

const nextElement = ally.query.nextTabbable(document.activeElement, {
  filter: '[data-focusable="true"]'
});
  • document.activeElement – текущий элемент с фокусом.
  • filter – CSS-селектор для ограничения области поиска.

Получение всех доступных для фокуса элементов

const focusableElements = ally.query.tabbable(document.body);

Этот метод возвращает массив элементов, которые можно сфокусировать клавишей Tab. В React это удобно использовать при создании модальных окон или навигационных меню, чтобы управлять фокусом внутри компонента.

Управление фокусом в React-компонентах

Для контроля фокуса в функциональных компонентах применяются хуки useEffect и useRef:

import { useEffect, useRef } from 'react';
import ally from 'ally.js';

function Modal({ isOpen }) {
  const modalRef = useRef(null);

  useEffect(() => {
    if (isOpen && modalRef.current) {
      const firstFocusable = ally.query.firstTabbable(modalRef.current);
      firstFocusable && firstFocusable.focus();
    }
  }, [isOpen]);

  return (
    <div ref={modalRef} role="dialog" aria-modal="true">
      <button>Закрыть</button>
      <input type="text" placeholder="Имя" />
    </div>
  );
}

Ключевые моменты:

  • Сохраняется ссылка на DOM-узел через useRef.
  • Фокус устанавливается только после монтирования компонента.
  • ally.js предоставляет функции firstTabbable, lastTabbable и nextTabbable для последовательного управления фокусом.

Создание ловушки фокуса (Focus Trap)

Focus Trap — это сценарий, когда пользовательский фокус остаётся внутри определённого блока, например, модального окна. ally.js упрощает его реализацию:

import ally from 'ally.js';

function trapFocus(container) {
  const handleKeyDown = (event) => {
    if (event.key !== 'Tab') return;

    const tabbable = ally.query.tabbable(container);
    if (!tabbable.length) return;

    const first = tabbable[0];
    const last = tabbable[tabbable.length - 1];

    if (event.shiftKey && document.activeElement === first) {
      last.focus();
      event.preventDefault();
    } else if (!event.shiftKey && document.activeElement === last) {
      first.focus();
      event.preventDefault();
    }
  };

  container.addEventListener('keydown', handleKeyDown);
  return () => container.removeEventListener('keydown', handleKeyDown);
}

В React это можно обернуть в useEffect для безопасного подключения и отключения обработчика:

useEffect(() => {
  if (!isOpen || !modalRef.current) return;
  const cleanup = trapFocus(modalRef.current);
  return cleanup;
}, [isOpen]);

Работа с ARIA-атрибутами

ally.js предоставляет методы для проверки доступности элементов:

const accessibilityCheck = ally.tools.toString(document.querySelector('button'));
console.log(accessibilityCheck);

Метод toString возвращает текстовое описание проблем, связанных с ARIA-атрибутами, видимостью и фокусируемостью. Это полезно для динамических компонентов, где контент меняется на лету.

Использование ally.js в асинхронных сценариях

В React часто встречаются асинхронные обновления DOM. ally.js позволяет безопасно находить элементы после рендеринга:

useEffect(() => {
  const timeout = setTimeout(() => {
    const firstInput = ally.query.firstTabbable(document.body);
    firstInput && firstInput.focus();
  }, 0);

  return () => clearTimeout(timeout);
}, []);

Такой подход гарантирует, что элементы уже присутствуют в DOM перед установкой фокуса.

Интеграция с динамическими списками

При работе с списками, которые подгружаются асинхронно или изменяются через состояние React, ally.js помогает управлять фокусом без лишних манипуляций:

const itemsRef = useRef([]);
const items = ['Элемент 1', 'Элемент 2', 'Элемент 3'];

useEffect(() => {
  const lastItem = ally.query.lastTabbable(document.getElementById('list'));
  lastItem && lastItem.focus();
}, [items]);

Преимущества:

  • Автоматическая фильтрация невидимых элементов.
  • Поддержка интерактивных элементов любого типа (button, input, a с href).
  • Простота интеграции с состояниями React.

Закрепление фокуса при закрытии модальных окон

Важно возвращать фокус на исходный элемент после закрытия диалога:

const triggerRef = useRef(null);

function openModal() {
  triggerRef.current && triggerRef.current.focus();
}

В сочетании с ally.query.firstTabbable это обеспечивает полную цикличность навигации клавиатурой и соответствие стандартам доступности.


ally.js в React обеспечивает гибкий, надёжный и удобный способ управления фокусом, упрощает работу с ARIA-атрибутами и гарантирует доступность компонентов без ручного контроля всех интерактивных элементов. Она особенно полезна для модальных окон, меню, динамических форм и списков, где управление фокусом вручную становится громоздким и подверженным ошибкам.