useFocusVisible и режимы ввода

Хук useFocusVisible предназначен для управления состоянием фокуса в приложениях на React с учетом режимов ввода пользователя. Он позволяет различать, был ли элемент сфокусирован с помощью клавиатуры (например, Tab) или мыши, что особенно важно для обеспечения доступности (accessibility) интерфейсов.

В стандартной HTML-практике браузеры автоматически показывают индикатор фокуса для клавиатуры, но при использовании мыши этот индикатор часто мешает визуальной эстетике. useFocusVisible решает эту проблему, предоставляя возможность контролировать видимость фокуса программно.


Импорт и базовое использование

import {useFocusVisible} from '@react-aria/interactions';

function Button() {
  const {isFocusVisible, focusProps} = useFocusVisible();

  return (
    <button {...focusProps} className={isFocusVisible ? 'focus-visible' : ''}>
      Click me
    </button>
  );
}

Объяснение ключевых моментов:

  • focusProps – набор пропсов, которые необходимо передать элементу, чтобы отслеживать фокус.
  • isFocusVisible – булево значение, которое указывает, нужно ли отображать индикатор фокуса.
  • Состояние isFocusVisible изменяется автоматически в зависимости от способа взаимодействия пользователя: клавиатура или мышь/тач.

Механизм определения режима ввода

React Aria использует алгоритм, аналогичный CSS-псевдоклассу :focus-visible:

  1. Keyboard – если пользователь взаимодействует через клавиатуру (Tab, Shift+Tab, Enter, стрелки), фокус считается видимым.
  2. Pointer – если фокус был установлен через мышь или сенсорный экран, индикатор фокуса скрывается.
  3. Programmatic – фокус, установленный скриптом (element.focus()), также учитывается, но отображение индикатора зависит от предыдущего режима ввода.

Это позволяет создавать интерфейсы, где визуальный индикатор фокуса появляется только тогда, когда он действительно нужен пользователю для навигации.


Интеграция с кастомными компонентами

При создании сложных компонентов, например, кастомных кнопок, полей ввода или списков с выбором, важно передавать focusProps внутрь интерактивного элемента, а не на обертку.

function CustomInput(props) {
  const {isFocusVisible, focusProps} = useFocusVisible();
  return (
    <div className="input-wrapper">
      <input {...props} {...focusProps} className={isFocusVisible ? 'focus-visible' : ''} />
    </div>
  );
}

Ключевой момент: индикатор фокуса применяется к элементу, который фактически получает фокус. Если передать focusProps на контейнер, но фокус на input, индикатор работать не будет.


Стилизация состояния фокуса

Для управления стилями состояния фокуса рекомендуется использовать класс .focus-visible. Например:

button {
  outline: none;
  border: 1px solid transparent;
}

button.focus-visible {
  outline: 2px solid #0078d4;
  outline-offset: 2px;
}

Преимущество этого подхода:

  • Сохраняется визуальная доступность для клавиатурных пользователей.
  • Убираются лишние обводки при кликах мышью.
  • Можно настроить любую кастомную анимацию или цвет, не влияя на стандартное поведение браузера.

Обработка сложных интеракций

useFocusVisible интегрируется с другими хуками из @react-aria/interactions, такими как usePress или useHover. Например, можно комбинировать фокус и нажатие для создания визуального отклика:

import {usePress} from '@react-aria/interactions';

function PressableButton() {
  const {isFocusVisible, focusProps} = useFocusVisible();
  const {pressProps, isPressed} = usePress();

  return (
    <button
      {...focusProps}
      {...pressProps}
      className={`${isFocusVisible ? 'focus-visible' : ''} ${isPressed ? 'pressed' : ''}`}
    >
      Press me
    </button>
  );
}

Принцип работы:

  • isFocusVisible управляет отображением индикатора фокуса.
  • isPressed позволяет добавить визуальный отклик при удержании или клике.
  • Стилизация реализуется через классы CSS, обеспечивая контроль над каждым состоянием.

Рекомендации по применению

  1. Всегда использовать focusProps на элементе, который принимает фокус.
  2. Не полагаться на глобальные селекторы CSS :focus, если проект требует контроля видимости фокуса.
  3. Комбинировать с другими хуками для обработки интерактивности (нажатие, наведение, drag&drop) для консистентного UX.
  4. Проверять поведение на разных устройствах: клавиатура, мышь, сенсорные экраны.

Практические примеры

Форма с полями ввода:

function LoginForm() {
  const usernameFocus = useFocusVisible();
  const passwordFocus = useFocusVisible();

  return (
    <form>
      <input
        type="text"
        placeholder="Username"
        {...usernameFocus.focusProps}
        className={usernameFocus.isFocusVisible ? 'focus-visible' : ''}
      />
      <input
        type="password"
        placeholder="Password"
        {...passwordFocus.focusProps}
        className={passwordFocus.isFocusVisible ? 'focus-visible' : ''}
      />
      <button type="submit">Login</button>
    </form>
  );
}

Вывод: каждое поле отслеживает состояние видимого фокуса отдельно, позволяя создавать удобные и доступные формы.


Заключение по ключевым принципам

  • useFocusVisible решает проблему различия визуального фокуса между клавиатурой и мышью.
  • Состояние isFocusVisible автоматически синхронизируется с режимом ввода пользователя.
  • Интеграция с другими хуками React Aria обеспечивает полноценное управление интерактивными компонентами.
  • Правильное использование focusProps и классов CSS гарантирует соблюдение стандартов доступности без ущерба для дизайна.