Композиция сложных компонентов

Radix UI предоставляет низкоуровневые, полностью контролируемые React-компоненты, которые позволяют строить сложные интерфейсы с высокой степенью кастомизации. Основная идея — разбиение компонентов на логическую основу и визуальное отображение, что обеспечивает гибкость при композиции.


Контейнеры и управляющие компоненты

В Radix UI многие сложные компоненты разделены на Root, Trigger, Content и другие вспомогательные элементы.

Пример структуры для модального окна:

import * as Dialog from '@radix-ui/react-dialog';

<Dialog.Root>
  <Dialog.Trigger>Открыть модалку</Dialog.Trigger>
  <Dialog.Overlay />
  <Dialog.Content>
    <Dialog.Title>Заголовок модалки</Dialog.Title>
    <Dialog.Description>Описание модального окна</Dialog.Description>
  </Dialog.Content>
</Dialog.Root>
  • Dialog.Root отвечает за логическое состояние (открыто/закрыто).
  • Dialog.Trigger — элемент, который инициирует открытие.
  • Dialog.Content содержит визуальную часть.
  • Dialog.Overlay создаёт фон с затемнением.

Такое разделение позволяет полностью контролировать внешний вид, при этом сохраняя доступность и правильное поведение.


Составные компоненты и контекст

Radix UI использует React Context для передачи состояния между вложенными компонентами. Это позволяет строить гибкие композиции без лишних пропсов.

Пример: Select

import * as Sel ect from '@radix-ui/react-select';

<Sel ect.Root>
  <Select.Trigger>
    <Select.Value placeholder="Выберите опцию" />
  </Select.Trigger>
  <Select.Content>
    <Select.Item value="option1">Опция 1</Select.Item>
    <Select.Item value="option2">Опция 2</Select.Item>
  </Select.Content>
</Select.Root>

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


Композиция с пользовательской логикой

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

Пример кастомного триггера для Popover:

import * as Popover fr om '@radix-ui/react-popover';

<Popover.Root>
  <Popover.Trigger asChild>
    <button className="custom-button">Открыть Popover</button>
  </Popover.Trigger>
  <Popover.Content>
    <div className="popover-content">Содержимое Popover</div>
  </Popover.Content>
</Popover.Root>

Ключевой момент — asChild, который позволяет передать собственный React-элемент в качестве триггера, сохраняя всю логику Radix UI.


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

Radix UI не включает визуальные анимации по умолчанию, но предоставляет состояния компонента, на основе которых можно создавать анимации через CSS или фреймворки типа Framer Motion.

[data-state="open"] {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.2s, transform 0.2s;
}

[data-state="closed"] {
  opacity: 0;
  transform: translateY(-10px);
}
  • Атрибут data-state автоматически ставится Radix UI.
  • Позволяет управлять стилями компонентов без дополнительной логики.

Сложные вложенные структуры

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

Пример меню с подменю:

import * as Menu fr om '@radix-ui/react-menu';

<Menu.Root>
  <Menu.Trigger>Меню</Menu.Trigger>
  <Menu.Content>
    <Menu.Item>Опция 1</Menu.Item>
    <Menu.Sub>
      <Menu.SubTrigger>Подменю</Menu.SubTrigger>
      <Menu.SubContent>
        <Menu.Item>Подопция 1</Menu.Item>
        <Menu.Item>Подопция 2</Menu.Item>
      </Menu.SubContent>
    </Menu.Sub>
  </Menu.Content>
</Menu.Root>
  • Menu.Sub создаёт вложенный уровень меню.
  • Контекст автоматически передаёт состояние между уровнями.
  • Логика открытия/закрытия подчиняется родительскому контексту.

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

Radix UI предоставляет возможность полностью контролировать состояние компонента извне через пропсы open, onOpenChange, value, onValueChange.

const [open, setOpen] = useState(false);

<Dialog.Root open={open} onOpenCha nge={setOpen}>
  <Dialog.Trigger>Открыть</Dialog.Trigger>
  <Dialog.Content>Контент модалки</Dialog.Content>
</Dialog.Root>
  • Позволяет синхронизировать состояние с Redux, Zustand или другими глобальными хранилищами.
  • Обеспечивает предсказуемое поведение при сложной композиции компонентов.

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

  1. Разделять логику и визуализацию — использовать Root/Trigger/Content вместо монолитных компонентов.
  2. Использовать asChild для кастомных элементов — сохраняется доступность и логика Radix.
  3. Строить сложные вложенные структуры через Sub-компоненты — меню, селекты, табы.
  4. Использовать контролируемые состояния при интеграции с глобальными стореджами — полный контроль над поведением.
  5. Применять CSS на основе data-атрибутов — удобная интеграция анимаций и стилей.

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