Управление с клавиатуры

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


Навигация с помощью стрелок

Floating UI предоставляет утилиты для обработки событий клавиш, особенно стрелок вверх (ArrowUp) и вниз (ArrowDown). Эти клавиши позволяют пользователю перемещаться по элементам списка или меню:

import { useFloating, shift, flip } from '@floating-ui/react';

const { refs, floatingStyles } = useFloating({
  placement: 'bottom-start',
  middleware: [flip(), shift({ padding: 5 })],
});

function handleKeyDown(event) {
  switch (event.key) {
    case 'ArrowDown':
      focusNextItem();
      break;
    case 'ArrowUp':
      focusPreviousItem();
      break;
  }
}

Особенности:

  • focusNextItem и focusPreviousItem управляют фокусом по DOM-элементам меню.
  • Обработка стрелок должна учитывать циклическую навигацию: при достижении последнего элемента следующий фокус возвращается к первому.

Открытие и закрытие через клавиши

Для интерактивных элементов Floating UI часто используют клавиши Enter, Space и Escape:

  • Enter/Space – активация элемента, открытие меню или выполнение действия.
  • Escape – закрытие плавающего окна и возврат фокуса на исходный элемент.

Пример:

function handleKeyDown(event) {
  if (event.key === 'Escape') {
    closeFloating();
    referenceRef.current.focus();
  }
  if (event.key === 'Enter' || event.key === ' ') {
    toggleFloating();
  }
}

Важные моменты:

  • Всегда возвращать фокус к исходному элементу после закрытия всплывающего окна.
  • Предусматривать возможность отмены действия через event.preventDefault() для предотвращения стандартного скролла при нажатии пробела.

Управление фокусом внутри Floating UI

Floating UI сам по себе не управляет фокусом, поэтому для удобной клавиатурной навигации часто комбинируют его с библиотеками вроде focus-trap или реализуют кастомные функции:

function focusFirstItem() {
  const firstItem = floatingRef.current.querySelector('[tabindex="0"]');
  firstItem?.focus();
}

function focusLastItem() {
  const items = floatingRef.current.querySelectorAll('[tabindex="0"]');
  items[items.length - 1]?.focus();
}

Рекомендации:

  • Все интерактивные элементы внутри всплывающего блока должны иметь tabindex="0" для корректного обхода с клавиатуры.
  • Использовать tabindex="-1" для элементов, которые не должны быть доступными напрямую через Tab, но могут получать фокус программно.

Обработка Tab и Shift+Tab

Правильная поддержка клавиши Tab обеспечивает логичный переход между элементами формы и всплывающими компонентами. Floating UI требует явного управления:

function handleTab(event) {
  const focusableItems = Array.from(
    floatingRef.current.querySelectorAll('[tabindex="0"]')
  );
  const currentIndex = focusableItems.indexOf(document.activeElement);

  if (event.shiftKey && currentIndex === 0) {
    focusableItems[focusableItems.length - 1].focus();
    event.preventDefault();
  } else if (!event.shiftKey && currentIndex === focusableItems.length - 1) {
    focusableItems[0].focus();
    event.preventDefault();
  }
}

Ключевой момент: циклический переход предотвращает потерю фокуса и закрытие меню по ошибке при стандартной навигации.


Интеграция с ARIA

Для доступности следует использовать ARIA-атрибуты совместно с клавиатурным управлением:

  • aria-haspopup="menu" – указывает, что элемент открывает всплывающее меню.
  • aria-expanded="true/false" – отражает текущее состояние.
  • role="menu" и role="menuitem" – помогают вспомогательным технологиям корректно интерпретировать структуру.

Пример:

<button aria-haspopup="menu" aria-expanded="false" id="menuButton">
  Открыть меню
</button>
<ul role="menu" id="menu" hidden>
  <li role="menuitem" tabindex="0">Пункт 1</li>
  <li role="menuitem" tabindex="0">Пункт 2</li>
</ul>

Объединение Floating UI и кастомных хендлеров

Floating UI предоставляет низкоуровневые инструменты для позиционирования, но клавиатурная логика строится вручную. Практический подход:

  1. Создать рефы для reference и floating элементов.
  2. Подключить middleware для корректного позиционирования (flip, shift).
  3. Настроить обработку клавиш (ArrowUp, ArrowDown, Enter, Escape, Tab).
  4. Реализовать функции управления фокусом и циклической навигации.
  5. Применить ARIA-атрибуты для полной доступности.

Такой подход обеспечивает полностью управляемое и доступное всплывающее взаимодействие с клавиатуры, сохраняя гибкость Floating UI.