Множественные диалоги

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

Структура компонентов Dialog

В Radix UI компонент диалога состоит из нескольких частей:

  • Dialog.Root — контейнер, управляющий состоянием диалога (open / closed).
  • Dialog.Trigger — элемент, который инициирует открытие диалога.
  • Dialog.Overlay — полупрозрачный слой, блокирующий взаимодействие с фоновым контентом.
  • Dialog.Content — собственно содержимое диалога, включая заголовки, формы и кнопки.
  • Dialog.Title и Dialog.Description — семантические элементы для улучшения доступности.
  • Dialog.Close — элемент, закрывающий диалог.

Для работы с множественными диалогами критически важно не смешивать состояния разных Root-компонентов, иначе возможны конфликты при закрытии и фокусировании.

Управление состоянием множественных диалогов

Radix UI использует контролируемый и неконтролируемый режимы:

  • В неконтролируемом режиме диалог самостоятельно управляет открытием через внутреннее состояние.
  • В контролируемом режиме состояние диалога хранится в переменной и передается через проп open.

Для нескольких диалогов рекомендуется использовать централизованное состояние, например через React useState или useReducer:

const [dialogs, setDialogs] = useState({
  dialogA: false,
  dialogB: false,
});

function openDialog(name) {
  setDialogs(prev => ({ ...prev, [name]: true }));
}

function closeDialog(name) {
  setDialogs(prev => ({ ...prev, [name]: false }));
}

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

Фокус и доступность

Одним из важнейших аспектов при работе с множественными диалогами является правильное управление фокусом:

  • Radix UI автоматически переносит фокус в открытый диалог, блокируя взаимодействие с другими элементами страницы.
  • При открытии нового диалога поверх существующего старый диалог может оставаться в DOM, но должен быть скрыт для скринридеров (aria-hidden).

Для достижения корректной последовательности фокусировки можно использовать Dialog.Content с пропом forceMount, чтобы диалог оставался в DOM даже закрытым, что предотвращает перерисовку при открытии нескольких окон.

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

  1. Вложенные диалоги: один диалог открывает другой.
<Dialog.Root open={dialogs.dialogA} onOpenCha nge={(open) => closeDialog('dialogA')}>
  <Dialog.Trigger>Открыть A</Dialog.Trigger>
  <Dialog.Content>
    Диалог A
    <Dialog.Root open={dialogs.dialogB} onOpenCha nge={(open) => setDialogs({...dialogs, dialogB: open})}>
      <Dialog.Trigger>Открыть B</Dialog.Trigger>
      <Dialog.Content>Диалог B</Dialog.Content>
    </Dialog.Root>
  </Dialog.Content>
</Dialog.Root>

Важно: всегда контролировать фокус и состояние через отдельные Root-компоненты, чтобы избежать «зависания» диалогов.

  1. Параллельные диалоги: два или более диалогов открываются независимо.

Использование отдельного состояния для каждого Root-компонента предотвращает конфликт между их Overlay и Content. Overlay каждого диалога может иметь различный стиль, чтобы визуально различать активные окна.

Стилизация и анимация

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

  • Слои Overlay могут иметь разную прозрачность и цвет, чтобы пользователь понимал иерархию открытых окон.
  • Анимации открытия и закрытия можно реализовать через CSS или библиотеку @stitches/react, используя классы data-state="open" и data-state="closed" для управления анимацией.
[data-state="open"] {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 200ms, transform 200ms;
}

[data-state="closed"] {
  opacity: 0;
  transform: translateY(-10px);
}

Лучшие практики

  • Каждому диалогу давать уникальные идентификаторы и отдельные состояния.
  • Для сложных интерфейсов использовать контролируемый режим, чтобы иметь полный контроль над последовательностью открытия и закрытия.
  • Следить за доступностью: заголовки, описание и правильное использование aria-hidden для скрытых диалогов.
  • Не забывать об управлении фокусом: при открытии нового диалога фокус должен переходить в верхний слой.

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