Библиотека Radix UI представляет собой набор низкоуровневых, доступных и полностью контролируемых компонентов интерфейса. Архитектура библиотеки предполагает композиционный подход: интерфейс формируется из множества небольших примитивов, которые объединяются в более сложные структуры.
Такая модель предоставляет большую гибкость, однако требует продуманной организации кода. Без структурирования проект с большим количеством компонентов Radix UI быстро становится трудным для поддержки.
Основные задачи организации кода:
Грамотно организованная структура позволяет использовать преимущества Radix UI — модульность, доступность и контроль над поведением компонентов.
Radix UI строится вокруг концепции компонентов-примитивов (primitives). Каждый интерфейсный элемент состоит из нескольких независимых частей.
Например, компонент Dialog включает:
Dialog.RootDialog.TriggerDialog.PortalDialog.OverlayDialog.ContentDialog.TitleDialog.DescriptionDialog.CloseКаждая часть выполняет конкретную роль. Такая архитектура требует аккуратной композиции.
Пример базовой структуры:
import * as Dialog from "@radix-ui/react-dialog";
function UserDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>Открыть</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="content">
<Dialog.Title>Профиль пользователя</Dialog.Title>
<Dialog.Description>
Информация о текущем пользователе
</Dialog.Description>
<Dialog.Close>Закрыть</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
При большом количестве подобных структур возникает необходимость выносить части интерфейса в отдельные компоненты.
В проектах с Radix UI удобно использовать многоуровневую структуру компонентов.
Компоненты, непосредственно использующие Radix UI.
components/primitives
Пример:
components/primitives/Dialog
components/primitives/Popover
components/primitives/DropdownMenu
Эти компоненты обычно:
Пример:
import * as Dialog from "@radix-ui/react-dialog";
export function Modal({ children, title }) {
return (
<Dialog.Root>
<Dialog.Trigger className="button">
Открыть
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="modal">
<Dialog.Title>{title}</Dialog.Title>
{children}
<Dialog.Close className="close">
Закрыть
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Компоненты пользовательского интерфейса, построенные на основе примитивов.
components/ui
Пример:
UserProfileModal
SettingsPopover
NotificationsMenu
import { Modal } from "../primitives/Modal";
export function UserProfileModal({ user }) {
return (
<Modal title="Профиль">
<div className="profile">
<p>{user.name}</p>
<p>{user.email}</p>
</div>
</Modal>
);
}
Компоненты, содержащие бизнес-логику.
features
Пример:
features/auth
features/dashboard
features/profile
import { UserProfileModal } from "@/components/ui/UserProfileModal";
export function ProfileSection({ user }) {
return (
<section>
<h2>Профиль</h2>
<UserProfileModal user={user} />
</section>
);
}
Такой подход предотвращает смешивание интерфейсной логики и бизнес-логики.
Типичная структура проекта с Radix UI:
src
├── components
│ ├── primitives
│ │ ├── Dialog
│ │ ├── Popover
│ │ ├── Tooltip
│ │
│ ├── ui
│ │ ├── UserMenu
│ │ ├── Modal
│ │ ├── NotificationList
│
├── features
│ ├── auth
│ ├── settings
│ ├── profile
│
├── hooks
│
├── utils
│
└── styles
Преимущества:
Одна из распространённых практик — скрывать прямое использование Radix UI внутри собственного слоя компонентов.
Это позволяет:
Пример обёртки для Tooltip.
import * as Tooltip from "@radix-ui/react-tooltip";
export function AppTooltip({ content, children }) {
return (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
{children}
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="tooltip">
{content}
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}
Использование:
<AppTooltip content="Удалить">
<button>?</button>
</AppTooltip>
Radix UI активно использует композиционный подход.
Компоненты не навязывают структуру, а позволяют создавать её вручную.
Пример композиции выпадающего меню:
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
export function UserMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger className="trigger">
Меню
</DropdownMenu.Trigger>
<DropdownMenu.Content className="menu">
<DropdownMenu.Item>
Профиль
</DropdownMenu.Item>
<DropdownMenu.Item>
Настройки
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>
Выход
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
Чтобы избежать дублирования, композиции часто разбиваются на более мелкие компоненты.
Часто встречающиеся структуры стоит оформлять в отдельные компоненты.
Пример — стандартный Dropdown Menu Item.
export function MenuItem({ children, onSelect }) {
return (
<DropdownMenu.Item
className="menu-item"
onSel ect={onSelect}
>
{children}
</DropdownMenu.Item>
);
}
Использование:
<MenuItem onSel ect={logout}>
Выйти
</MenuItem>
Такой подход обеспечивает:
Radix UI поддерживает контролируемый и неконтролируемый режимы.
Состояние управляется внутри компонента.
<Dialog.Root>
Это самый простой вариант.
Состояние управляется внешним компонентом.
const [open, setOpen] = useState(false);
<Dialog.Root open={open} onOpenCha nge={setOpen}>
Контролируемый режим полезен:
Повторяющуюся логику удобно выносить в хуки.
Пример:
import { useState } from "react";
export function useModal() {
const [open, setOpen] = useState(false);
function openModal() {
setOpen(true);
}
function closeModal() {
setOpen(false);
}
return {
open,
openModal,
closeModal,
setOpen
};
}
Использование:
const modal = useModal();
<Dialog.Root
open={modal.open}
onOpenCha nge={modal.setOpen}
>
Radix UI не предоставляет встроенных стилей. Поэтому стили должны быть организованы отдельно.
Популярные подходы:
Пример с CSS Modules:
Dialog.module.css
.overlay {
background: rgba(0,0,0,0.5);
}
.content {
background: white;
border-radius: 8px;
}
Использование:
import styles from "./Dialog.module.css";
<Dialog.Content className={styles.content}>
asChildОдной из ключевых особенностей Radix является свойство
asChild, позволяющее заменять стандартный
элемент.
<Tooltip.Trigger asChild>
<button>Кнопка</button>
</Tooltip.Trigger>
Без asChild компонент создаст собственный
DOM-элемент.
С asChild используется переданный элемент.
Преимущества:
Многие компоненты Radix используют Portal для рендеринга вне текущего DOM-дерева.
Пример:
<Dialog.Portal>
Это необходимо для:
Portal предотвращает проблемы:
overflowz-indexНесколько практик помогают поддерживать высокую производительность:
1. Ленивая загрузка компонентов
const SettingsModal = lazy(() => import("./SettingsModal"));
2. Разделение больших компонентов
Интерфейсные блоки должны быть маленькими и специализированными.
3. Мемоизация
const MemoMenu = React.memo(UserMenu);
4. Вынос логики
Логика не должна находиться внутри сложных JSX-структур.
Некоторые компоненты Radix используют Context API.
Для сложных интерфейсов можно создавать собственные контексты.
Пример:
const ModalContext = createContext(null);
export function ModalProvider({ children }) {
const modal = useModal();
return (
<ModalContext.Provider value={modal}>
{children}
</ModalContext.Provider>
);
}
Это позволяет управлять модальными окнами из разных частей интерфейса.
Radix UI относится к категории headless библиотек.
Особенности:
Организация кода должна учитывать этот принцип:
Такой подход делает интерфейс гибким и независимым от конкретной дизайн-системы.