Группировка опций

React Aria предоставляет мощные инструменты для создания доступных интерфейсов с использованием JavaScript и React. Одной из ключевых задач при работе с компонентами выбора, такими как Select или ListBox, является корректная группировка опций для улучшения навигации и восприятия пользователем. Группировка опций позволяет логически объединять элементы списка, облегчая поиск и понимание их структуры.


Структура данных для группировки

В React Aria группы опций представляются объектами с полями type, name и children. Стандартная структура выглядит следующим образом:

const options = [
  {
    type: 'section',
    name: 'Фрукты',
    children: [
      { type: 'item', name: 'Яблоко' },
      { type: 'item', name: 'Банан' },
      { type: 'item', name: 'Апельсин' }
    ]
  },
  {
    type: 'section',
    name: 'Овощи',
    children: [
      { type: 'item', name: 'Морковь' },
      { type: 'item', name: 'Брокколи' },
      { type: 'item', name: 'Перец' }
    ]
  }
];
  • type: 'section' обозначает группу элементов.
  • name — это название группы, которое будет отображаться пользователю.
  • children — массив элементов внутри группы, каждый с типом item.

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


Использование useListBox и useListBoxSection

Для реализации списка с группами опций React Aria предоставляет хуки useListBox и useListBoxSection.

import { useListBox, useListBoxSection } from '@react-aria/listbox';
import { useTreeState } from '@react-stately/tree';

function MyListBox({ options }) {
  const state = useTreeState({ items: options });

  return (
    <ul {...useListBox({ selectionMode: 'single' }, state)}>
      {state.collection.map((item) =>
        item.type === 'section' ? (
          <ListBoxSection key={item.key} section={item} />
        ) : (
          <ListBoxItem key={item.key} item={item} />
        )
      )}
    </ul>
  );
}

function ListBoxSection({ section }) {
  const { itemProps, headingProps, groupProps } = useListBoxSection({ section });
  return (
    <li {...groupProps}>
      <div {...headingProps}>{section.name}</div>
      <ul>
        {section.children.map((item) => (
          <ListBoxItem key={item.key} item={item} />
        ))}
      </ul>
    </li>
  );
}

function ListBoxItem({ item }) {
  return <li>{item.name}</li>;
}

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

  • useTreeState формирует состояние коллекции с поддержкой разделов и элементов.
  • useListBoxSection автоматически добавляет необходимые атрибуты для заголовков и групп, включая aria-labelledby и role="group".
  • Каждой группе и элементу присваивается уникальный key, что важно для корректного рендеринга в React.

Доступность и навигация

React Aria автоматически обрабатывает навигацию с клавиатуры для списков с секциями. Пользователь может:

  • Перемещаться между элементами стрелками вверх/вниз.
  • Перемещаться между группами через клавиши Home и End.
  • Легко распознавать группы с помощью aria-labelledby и семантических ролей (group и listbox).

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


Вложенные группы

React Aria поддерживает вложенные группы, позволяя создавать многоуровневые списки:

const nestedOptions = [
  {
    type: 'section',
    name: 'Континенты',
    children: [
      {
        type: 'section',
        name: 'Европа',
        children: [
          { type: 'item', name: 'Франция' },
          { type: 'item', name: 'Германия' }
        ]
      },
      {
        type: 'section',
        name: 'Азия',
        children: [
          { type: 'item', name: 'Япония' },
          { type: 'item', name: 'Китай' }
        ]
      }
    ]
  }
];

Использование вложенных групп аналогично одноуровневым секциям, важно лишь корректно обрабатывать children при рендеринге с useListBoxSection.


Стилизация групп

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

li[group] > div {
  font-weight: bold;
  margin-top: 1em;
  color: #333;
}

li[item] {
  padding: 4px 8px;
}

Это позволяет создавать списки с визуальной иерархией, которая соответствует логике группировки.


Динамическое обновление групп

Состояние коллекции можно обновлять динамически:

state.insertItem({ type: 'item', name: 'Груша' }, state.collection.get('Фрукты'));
state.removeItem(state.collection.get('Брокколи'));

React Aria автоматически обновляет рендер и навигацию, сохраняя доступность.


Практические рекомендации

  • Использовать type: 'section' для всех групп.
  • Всегда назначать уникальные key элементам и секциям.
  • Сохранять семантическую структуру section → item для корректной работы скринридеров.
  • При вложенных группах тщательно обрабатывать children и aria-атрибуты.
  • Использовать стили для визуального разделения групп, но не нарушать доступность.

Группировка опций в React Aria является важной частью создания интерфейсов, которые одновременно логичны, удобны и доступны. Корректная структура данных, использование хуков useListBox и useListBoxSection, а также внимание к доступности обеспечивают надежный и масштабируемый пользовательский опыт.