useListBox для списков опций

useListBox — это хук из библиотеки React Aria, предназначенный для управления списками опций в компонентах интерфейса, таких как выпадающие меню, селекты и списки с поддержкой клавиатуры. Он обеспечивает доступность (accessibility) на уровне WAI-ARIA, автоматически добавляя необходимые атрибуты и события для взаимодействия с экранными считывателями и клавиатурой.

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


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

Для начала требуется импортировать необходимые хуки и типы из React Aria и React Stately:

import { useListBox, useOption } from '@react-aria/listbox';
import { useListState } from '@react-stately/list';

useListState управляет внутренним состоянием списка: выбранные элементы, фокус, состояние disabled и прочее. useListBox использует это состояние для генерации атрибутов ARIA.

Простейший пример базового списка:

function ListBoxExample(props) {
  let state = useListState(props);
  let ref = React.useRef();
  let { listBoxProps } = useListBox(props, state, ref);

  return (
    <ul {...listBoxProps} ref={ref}>
      {[...state.collection].map(item => (
        <Option key={item.key} item={item} state={state} />
      ))}
    </ul>
  );
}

function Option({ item, state }) {
  let ref = React.useRef();
  let { optionProps, isSelected, isFocused } = useOption({ key: item.key }, state, ref);

  return (
    <li
      {...optionProps}
      ref={ref}
      style={{
        background: isFocused ? 'lightblue' : 'transparent',
        fontWeight: isSelected ? 'bold' : 'normal',
      }}
    >
      {item.rendered}
    </li>
  );
}

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

  • listBoxProps содержит все необходимые атрибуты ARIA (role="listbox", aria-multiselectable, обработчики клавиатуры).
  • optionProps — атрибуты для каждой опции (role="option", aria-selected, aria-disabled).
  • Состояние isFocused и isSelected позволяет управлять визуальной подсветкой и стилями.

Поддержка клавиатуры и навигации

useListBox автоматически интегрируется с клавиатурной навигацией:

  • Стрелки вверх/вниз перемещают фокус между опциями.
  • Enter или Space выбирают опцию.
  • Home/End переходят к первой или последней опции.
  • Page Up/Page Down (при длинных списках) могут перемещать фокус группами.

При этом разработчик не должен вручную писать обработчики клавиатуры — достаточно передать listBoxProps корневому элементу списка.


Множественный выбор (Multi-select)

Для списков с множественным выбором используется selectionMode: "multiple" при инициализации состояния:

let state = useListState({
  selectionMode: 'multiple',
  items: ['Яблоко', 'Банан', 'Груша']
});

В таком режиме useOption автоматически обрабатывает выделение нескольких элементов и выставляет aria-selected корректно для каждой опции.


Disabled элементы и группы

Опции и группы можно помечать как disabled:

let items = [
  { name: 'Фрукты', key: 'fruits', children: [
    { name: 'Яблоко', key: 'apple', isDisabled: false },
    { name: 'Банан', key: 'banana', isDisabled: true }
  ]}
];

useOption установит aria-disabled="true" для опций с isDisabled: true, что предотвратит выбор таких элементов и корректно сообщит об этом экранным считывателям.

Для групп можно использовать role="group" и aria-labelledby, что поддерживается в коллекциях React Stately.


Пользовательские стили и рендеринг

useListBox и useOption не навязывают визуальные решения. Стиль и анимации полностью на стороне разработчика. Можно использовать условные стили для:

  • Выделенной опции (isSelected)
  • Фокусированной опции (isFocused)
  • Disabled опции (isDisabled)

Пример стилизации:

style={{
  padding: '8px 12px',
  cursor: isDisabled ? 'not-allowed' : 'pointer',
  background: isFocused ? '#e0f7ff' : 'transparent',
  color: isDisabled ? '#aaa' : '#000',
}}

Управление фокусом и виртуализация

Для длинных списков часто используют виртуализацию через react-virtual или react-window. В этом случае useListBox продолжает корректно управлять фокусом и выделением, если опции рендерятся динамически, потому что все вычисления состояния происходят через useListState.


Поддержка экранных считывателей

useListBox автоматически добавляет атрибуты ARIA:

  • role="listbox" на корневой элемент.
  • aria-multiselectable="true/false" для множественного выбора.
  • role="option" на каждой опции.
  • aria-selected для выбранных элементов.
  • aria-disabled для недоступных опций.
  • Обновление фокуса через aria-activedescendant.

Это позволяет создавать полностью доступные списки без ручного вмешательства.


Сложные сценарии

useListBox можно комбинировать с:

  • Поиском по списку — фильтруем коллекцию через useListState и ререндерим список.
  • Динамическим добавлением/удалением элементов — состояние обновляется через items коллекции.
  • Комбинацией с меню и Popover — список можно вложить в выпадающий компонент, управляя фокусом через useOverlay.

useListBox в связке с useOption и useListState предоставляет мощный, гибкий и доступный способ работы с интерактивными списками в React, освобождая от необходимости вручную управлять фокусом, выделением и атрибутами ARIA. Такой подход гарантирует совместимость с клавиатурой, экранными считывателями и различными сценариями сложного UI.