Множественный выбор

Множественный выбор — один из базовых паттернов пользовательских интерфейсов. Он используется в случаях, когда необходимо предоставить пользователю возможность выбрать несколько элементов из набора, в отличие от радиокнопок, допускающих только одно значение.

Типичные сценарии применения:

  • выбор категорий
  • фильтрация данных
  • настройка параметров
  • выбор тегов
  • управление доступными функциями
  • выбор нескольких опций формы

В библиотеке Radix UI множественный выбор реализуется через несколько компонентов:

  • ToggleGroup (multiple mode)
  • Checkbox
  • DropdownMenu с CheckboxItem
  • Sel ect-подобные интерфейсы с кастомной логикой
  • Combobox с множественным выбором

Каждый из них решает разные задачи и обладает собственной архитектурой взаимодействия.


ToggleGroup как компонент множественного выбора

Компонент ToggleGroup позволяет создавать группы кнопок, которые могут находиться в активном или неактивном состоянии.

Radix UI поддерживает два режима работы:

  • type="single" — только одно значение
  • type="multiple" — множественный выбор

Для реализации множественного выбора используется режим multiple.


Базовая структура ToggleGroup

import * as ToggleGroup fr om "@radix-ui/react-toggle-group";

function Example() {
  return (
    <ToggleGroup.Root type="multiple">
      <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
      <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
      <ToggleGroup.Item value="underline">U</ToggleGroup.Item>
    </ToggleGroup.Root>
  );
}

Компоненты структуры:

Компонент Назначение
ToggleGroup.Root контейнер группы
ToggleGroup.Item элемент выбора

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

При использовании type="multiple":

  • каждый Item имеет значение value
  • выбранные элементы хранятся в виде массива
  • включение/выключение элемента изменяет массив

Пример состояния:

["bold", "italic"]

Это означает, что активированы два элемента.


Управляемый и неуправляемый режим

Radix UI поддерживает два режима управления состоянием.

Неуправляемый компонент

<ToggleGroup.Root type="multiple" defaultValue={["bold"]}>
  <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
  <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
</ToggleGroup.Root>

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

  • состояние хранится внутри компонента
  • используется defaultValue
  • React состояние не требуется

Управляемый компонент

const [formats, setFormats] = useState(["bold"]);

<ToggleGroup.Root
  type="multiple"
  value={formats}
  onValueCha nge={setFormats}
>
  <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
  <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
</ToggleGroup.Root>

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

  • массив выбранных значений хранится в состоянии React
  • управление полностью контролируется приложением

Обработка выбора элементов

Функция onValueChange получает массив активных значений.

function handleChange(values) {
  console.log(values);
}

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

<ToggleGroup.Root
  type="multiple"
  onValueCha nge={handleChange}
>

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


Атрибуты доступности (Accessibility)

Radix UI изначально реализует корректную доступность.

ToggleGroup автоматически добавляет:

  • role="group"
  • aria-pressed
  • клавиатурную навигацию

Каждый элемент:

aria-pressed="true"

или

aria-pressed="false"

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


Клавиатурная навигация

Поддержка клавиатуры встроена.

Доступные действия:

Клавиша Действие
Tab переход к группе
ArrowLeft предыдущий элемент
ArrowRight следующий элемент
Space переключение
Enter переключение

Это соответствует стандартам WAI-ARIA Toggle Button Group.


Стилизация активных элементов

Radix UI добавляет атрибут состояния:

data-state="on"

или

data-state="off"

Это позволяет легко стилизовать активные кнопки.

Пример CSS:

[data-state="on"] {
  background: black;
  color: white;
}

Ограничение количества выбранных элементов

Radix UI не ограничивает количество выбранных элементов, однако это можно реализовать логикой приложения.

Пример:

function handleChange(values) {
  if (values.length <= 3) {
    setFormats(values);
  }
}

Теперь можно выбрать максимум три элемента.


Реализация фильтрации с множественным выбором

Один из самых распространённых сценариев — фильтрация.

Пример:

const [filters, setFilters] = useState([]);

<ToggleGroup.Root
  type="multiple"
  value={filters}
  onValueCha nge={setFilters}
>
  <ToggleGroup.Item value="react">React</ToggleGroup.Item>
  <ToggleGroup.Item value="vue">Vue</ToggleGroup.Item>
  <ToggleGroup.Item value="angular">Angular</ToggleGroup.Item>
</ToggleGroup.Root>

Выбранные фильтры:

["react", "angular"]

Затем используется фильтрация данных:

items.filter(item => filters.includes(item.framework))

Checkbox как механизм множественного выбора

Checkbox — наиболее классический способ множественного выбора.

Radix UI предоставляет компонент:

@radix-ui/react-checkbox

Базовый пример

import * as Checkbox from "@radix-ui/react-checkbox";

function Example() {
  return (
    <label>
      <Checkbox.Root value="email">
        <Checkbox.Indicator />
      </Checkbox.Root>
      Email notifications
    </label>
  );
}

Контроль состояния

const [checked, setChecked] = useState(false);

<Checkbox.Root
  checked={checked}
  onCheckedCha nge={setChecked}
>
  <Checkbox.Indicator />
</Checkbox.Root>

Массив значений

Для множественного выбора используется массив.

const [selected, setSelected] = useState([]);

Функция изменения:

function toggle(value) {
  setSelected(prev =>
    prev.includes(value)
      ? prev.filter(v => v !== value)
      : [...prev, value]
  );
}

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

<Checkbox.Root
  checked={selected.includes("email")}
  onCheckedCha nge={() => toggle("email")}
>
  <Checkbox.Indicator />
</Checkbox.Root>

DropdownMenu с множественным выбором

В выпадающих меню часто требуется выбирать несколько элементов.

Radix UI предоставляет:

DropdownMenu.CheckboxItem

Пример

import * as DropdownMenu from "@radix-ui/react-dropdown-menu";

function Example() {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger>
        Filters
      </DropdownMenu.Trigger>

      <DropdownMenu.Content>

        <DropdownMenu.CheckboxItem checked>
          React
        </DropdownMenu.CheckboxItem>

        <DropdownMenu.CheckboxItem>
          Vue
        </DropdownMenu.CheckboxItem>

      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
}

Управление состоянием

const [filters, setFilters] = useState(["react"]);
checked={filters.includes("react")}

Изменение:

onCheckedCha nge={(checked) =>
  updateFilter("react", checked)
}

Реализация множественного Select

Radix UI не предоставляет готовый multi-select, однако его легко построить на базе:

  • Popover
  • Command
  • Checkbox
  • ToggleGroup

Архитектура:

Trigger
  ↓
Popover
  ↓
Список элементов
  ↓
Checkbox

Пример структуры

<Popover.Root>

  <Popover.Trigger>
    Select frameworks
  </Popover.Trigger>

  <Popover.Content>

    <Checkbox value="react" />
    <Checkbox value="vue" />
    <Checkbox value="angular" />

  </Popover.Content>

</Popover.Root>

Управление списком выбранных элементов

Выбранные элементы можно отображать в виде тегов.

Пример:

selected.map(item => (
  <Tag key={item}>{item}</Tag>
))

Оптимизация производительности

При большом количестве элементов (1000+) важно учитывать производительность.

Основные методы оптимизации:

1. Virtualization

Использование библиотек:

  • react-window
  • react-virtual

Позволяет рендерить только видимые элементы.


2. Мемоизация элементов

const Item = React.memo(function Item({ value }) {
  ...
});

Это предотвращает лишние перерисовки.


3. Стабильные колбэки

const toggle = useCallback((value) => {
  ...
}, []);

Сложные сценарии множественного выбора

Выбор всех элементов

Часто требуется кнопка:

Select all

Пример:

function selectAll() {
  setSelected(items.map(i => i.id));
}

Снятие выбора

Clear selection
setSelected([]);

Частично выбранное состояние

Checkbox поддерживает состояние:

indeterminate

Это полезно при иерархических структурах.

Пример:

Parent
 ├ Child
 ├ Child
 └ Child

Если выбран только один дочерний элемент, родитель имеет состояние indeterminate.


Иерархический множественный выбор

Radix UI можно использовать для построения древовидных структур.

Пример:

Folder
 ├ File
 ├ File
 └ File

Логика:

  • выбор папки выбирает все элементы
  • снятие снимает выбор
  • частичный выбор отображается как indeterminate

Стилизация ToggleGroup

Пример кнопок форматирования:

.toggle-group {
  display: flex;
  gap: 4px;
}

.toggle-item {
  padding: 6px 10px;
  border: 1px solid #ccc;
}

.toggle-item[data-state="on"] {
  background: black;
  color: white;
}

Анимация состояния

Анимацию можно добавить через CSS.

.toggle-item {
  transition: background 0.2s;
}

Или через библиотеку:

Framer Motion

Типичные ошибки

Использование single вместо multiple

type="single"

В этом случае массив значений не работает.


Дублирование value

Каждый элемент должен иметь уникальный value.

Неправильно:

value="react"
value="react"

Потеря состояния

Ошибка возникает, если ключи списка нестабильны.

key={index}

Следует использовать уникальный идентификатор.


Архитектура Radix UI для множественного выбора

Radix придерживается принципов:

  • Headless UI
  • Composable architecture
  • Accessibility-first
  • State control flexibility

Это означает:

  • UI полностью настраивается
  • логика отделена от внешнего вида
  • можно комбинировать компоненты

Когда использовать разные подходы

Сценарий Компонент
панель кнопок ToggleGroup
форма Checkbox
фильтр в меню DropdownMenu.CheckboxItem
сложный селектор Popover + Checkbox
поиск с выбором Combobox

Практический пример: панель форматирования

<ToggleGroup.Root
  type="multiple"
  className="toolbar"
>

  <ToggleGroup.Item value="bold">
    Bold
  </ToggleGroup.Item>

  <ToggleGroup.Item value="italic">
    Italic
  </ToggleGroup.Item>

  <ToggleGroup.Item value="underline">
    Underline
  </ToggleGroup.Item>

</ToggleGroup.Root>

Результат:

  • пользователь может выбрать несколько форматов
  • состояние передаётся редактору текста
  • интерфейс соответствует стандартам доступности

Расширение возможностей

Radix UI легко интегрируется с:

  • React Hook Form
  • Zustand
  • Redux
  • TanStack Query

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


Внутренний механизм ToggleGroup

ToggleGroup использует:

  • React Context
  • Compound Components
  • Controlled / uncontrolled state

Алгоритм:

  1. Root создаёт контекст
  2. Items подписываются на контекст
  3. состояние обновляется через onValueChange
  4. элементы получают новое состояние

Это обеспечивает:

  • синхронизацию
  • минимальные перерисовки
  • гибкость API

Поведение при удалении элемента

Если выбранный элемент исчезает из списка:

value больше не существует

React автоматически исключает его из интерфейса, но состояние необходимо синхронизировать.

Рекомендуется фильтрация:

setSelected(prev =>
  prev.filter(v => available.includes(v))
);

Интеграция с формами

Пример с HTML формой:

<input type="hidden" name="filters" value={selected.join(",")} />

Это позволяет отправить выбранные значения на сервер.


Сериализация состояния

Часто используется хранение в URL.

Пример:

?filters=react,vue

Парсинг:

const filters = searchParams.get("filters").split(",");

Масштабирование интерфейса множественного выбора

При разработке сложных систем применяются:

  • виртуализация списков
  • ленивые загрузки
  • серверная фильтрация
  • дебаунс поиска

Это позволяет использовать множественный выбор даже для десятков тысяч элементов.