useFocusRing и индикация фокуса

Хук useFocusRing из библиотеки React Aria предназначен для управления визуальной индикацией фокуса элементов пользовательского интерфейса. Он обеспечивает согласованное поведение фокуса, учитывая клавиатурные и мышиные взаимодействия, что особенно важно для доступности (accessibility) веб-приложений.

useFocusRing возвращает объект с двумя ключевыми свойствами:

  • isFocused – булевое значение, показывающее, находится ли элемент в фокусе.
  • isFocusVisible – булевое значение, определяющее, должна ли отображаться визуальная подсветка фокуса, обычно активируется клавиатурной навигацией.

Дополнительно возвращается объект focusProps, который содержит необходимые обработчики событий (onFocus, onBlur, onFocusChange) для правильной работы индикации фокуса. Его следует распространять на элемент интерфейса:

import {useFocusRing} from '@react-aria/focus';

function Button(props) {
  let {focusProps, isFocusVisible} = useFocusRing();
  return (
    <button {...focusProps} className={isFocusVisible ? 'focus-ring' : ''}>
      {props.children}
    </button>
  );
}

Отличие isFocused и isFocusVisible

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

Таким образом, isFocusVisible контролирует отображение фокусных рамок, соблюдая рекомендации WCAG.

Настройка стилей фокусной рамки

Чаще всего для отображения фокуса используется CSS-класс с псевдоклассами или стилями:

.focus-ring {
  outline: 2px solid #007aff;
  outline-offset: 2px;
  border-radius: 4px;
}

Комбинируя focusProps и isFocusVisible, можно динамически применять этот класс только при необходимости:

<button
  {...focusProps}
  className={isFocusVisible ? 'focus-ring' : ''}
>
  Отправить
</button>

Такой подход исключает появление визуальных индикаторов фокуса при кликах мышью, сохраняя интерфейс чистым и доступным.

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

useFocusRing особенно полезен в составных элементах, таких как модальные окна, меню, табы или списки с выбором. В таких случаях можно комбинировать хук с useFocusManager или useTabList для централизованного контроля фокуса внутри компонента.

Пример для списка кнопок:

import {useFocusRing} from '@react-aria/focus';

function MenuItem({children}) {
  let {focusProps, isFocusVisible} = useFocusRing();
  return (
    <li>
      <button {...focusProps} className={isFocusVisible ? 'focus-ring' : ''}>
        {children}
      </button>
    </li>
  );
}

Каждая кнопка получает независимую подсветку фокуса, что повышает удобство навигации с клавиатуры.

Совместимость с другими хуками React Aria

useFocusRing можно использовать вместе с:

  • useButton – добавляет базовую функциональность кнопки и совместимо с фокусом.
  • useTextField – управляет фокусом текстовых полей с правильной визуальной индикацией.
  • useSelectableItem – для выбора элементов в списках с визуальной подсветкой текущего фокуса.

Пример комбинированного использования с useButton:

import {useButton} from '@react-aria/button';
import {useFocusRing} from '@react-aria/focus';

function CustomButton(props) {
  let {buttonProps} = useButton(props);
  let {focusProps, isFocusVisible} = useFocusRing();
  
  return (
    <button
      {...buttonProps}
      {...focusProps}
      className={isFocusVisible ? 'focus-ring' : ''}
    >
      {props.children}
    </button>
  );
}

Адаптивная визуальная подсветка

Для улучшения UX можно динамически менять стили фокуса в зависимости от состояния или темы:

className={`${isFocusVisible ? 'focus-ring' : ''} ${isDisabled ? 'disabled' : ''}`}

Это позволяет сохранять согласованность с дизайн-системой и избегать конфликтов с другими визуальными состояниями компонента.

Итоговый подход

Основной принцип работы с useFocusRing:

  1. Распространять focusProps на интерактивный элемент.
  2. Использовать isFocusVisible для условного применения визуальной подсветки.
  3. Не полагаться на :focus в CSS для всех случаев, особенно если нужно различать клавиатурный и мышиный фокус.
  4. Комбинировать с другими хуками React Aria для комплексных интерфейсов.

Этот подход обеспечивает доступность, соблюдение стандартов WCAG и чистый UX без лишних визуальных отвлекающих элементов.