Radix UI предоставляет набор низкоуровневых, но гибких компонентов для построения интерфейсов в React. Одним из ключевых инструментов является портал (Portal) — механизм, позволяющий рендерить компонент в DOM вне иерархии родительского элемента. Это особенно важно для модальных окон, тултипов, поповер-меню и других всплывающих элементов, которые должны визуально выходить за пределы контейнера, сохраняя правильное позиционирование и управление фокусом.
В Radix UI портал реализуется через компонент
<Portal>, который рендерит дочерний
элемент в document.body или в указанный контейнер. Пример
базового использования:
import * as Portal from '@radix-ui/react-portal';
<Portal.Root>
<div className="modal-content">
Содержимое модального окна
</div>
</Portal.Root>
Ключевые моменты:
<Portal.Root>
рендерятся вне родительской структуры компонента.При сложных интерфейсах часто возникает необходимость
вложенных порталов. Radix UI поддерживает вложенные
<Portal> без потери управления фокусом и событий:
<Portal.Root>
<div className="tooltip">
<Portal.Root>
<div className="tooltip-inner">
Вложенное содержимое
</div>
</Portal.Root>
</div>
</Portal.Root>
Особенности:
По умолчанию, порталы рендерятся в document.body, но
часто требуется рендеринг внутри специфического контейнера:
const container = document.getElementById('custom-root');
<Portal.Root container={container}>
<div>Контент в кастомном контейнере</div>
</Portal.Root>
Преимущества кастомного контейнера:
z-index.Radix UI порталы поддерживают анимации через стандартные CSS-переходы
или сторонние библиотеки анимаций, например Framer
Motion. Важный аспект — анимации должны быть применены к
контенту портала, а не к <Portal.Root>, поскольку сам
портал является оберткой без визуального рендера:
import { motion } from 'framer-motion';
<Portal.Root>
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
Анимированное содержимое
</motion.div>
</Portal.Root>
Лучшие практики:
<Portal.Root>.Radix UI обеспечивает правильное управление фокусом при использовании порталов. Это критично для модальных окон и поповер-меню:
Esc для закрытия
модального окна) обрабатываются корректно даже при рендеринге вне
родителя.Пример с управлением фокусом:
import * as FocusScope from '@radix-ui/react-focus-scope';
<Portal.Root>
<FocusScope.Root>
<button>Закрыть</button>
<input placeholder="Введите текст" />
</FocusScope.Root>
</Portal.Root>
Порталы в Radix UI часто используются вместе с компонентами Dialog, Popover, Tooltip, обеспечивая:
Пример интеграции с диалоговым окном:
import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Root>
<Dialog.Trigger>Открыть модальное окно</Dialog.Trigger>
<Dialog.Overlay />
<Portal.Root>
<Dialog.Content>
Содержимое диалога с порталом
</Dialog.Content>
</Portal.Root>
</Dialog.Root>
Особенности:
<Dialog.Content> можно безопасно рендерить через
портал, сохраняя доступность (aria-атрибуты) и обработку
клавиш.<Portal.Root> для всех всплывающих
элементов, которые выходят за пределы родительского контейнера.Порталы Radix UI обеспечивают гибкость, масштабируемость и доступность интерфейсов, делая возможным создание сложных пользовательских элементов без потери контроля над DOM, стилями и событиями.