Для интеграции Radix UI с приложением на Remix
необходимо сначала установить базовые пакеты. Основным пакетом является
@radix-ui/react-*, где * обозначает конкретный
компонент (Dialog, Popover, DropdownMenu и др.). Обычно вместе с Radix
используют стилизаторы, например Stitches, но можно
обойтись и обычным CSS или Tailwind.
Пример установки для Dialog и Popover:
npm install @radix-ui/react-dialog @radix-ui/react-popover
npm install @stitches/react
После установки создаётся базовая структура проекта Remix, где
компоненты Radix можно использовать в UI-подкомпонентах
(components/) или непосредственно в страницах
(routes/).
Radix UI предоставляет контролируемые и неконтролируемые компоненты.
state и setState.Пример использования Dialog с контролируемым состоянием:
import * as Dialog from '@radix-ui/react-dialog';
import { useState } from 'react';
export default function MyDialog() {
const [open, setOpen] = useState(false);
return (
<Dialog.Root open={open} onOpenCha nge={setOpen}>
<Dialog.Trigger>Открыть диалог</Dialog.Trigger>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Заголовок</Dialog.Title>
<Dialog.Description>Описание диалога</Dialog.Description>
<button onCl ick={() => setOpen(false)}>Закрыть</button>
</Dialog.Content>
</Dialog.Root>
);
}
Ключевые моменты:
Dialog.Root управляет состоянием
открытия/закрытия.Dialog.Trigger — элемент, который инициирует
открытие.Dialog.Overlay обеспечивает затемнение фона.Dialog.Content содержит весь контент диалога.Radix UI компоненты работают как обычные React-компоненты, поэтому их
можно встраивать в страницы Remix
(routes/) и использовать вместе с loader и
action.
Например, диалог для подтверждения действия с формой Remix:
import * as Dialog from '@radix-ui/react-dialog';
import { Form } from '@remix-run/react';
export default function DeleteDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>Удалить запись</Dialog.Trigger>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Подтвердите удаление</Dialog.Title>
<Form method="post">
<button type="submit">Удалить</button>
</Form>
</Dialog.Content>
</Dialog.Root>
);
}
Особенности взаимодействия:
Form из Remix может быть встроен в любой Radix UI
компонент без ограничений.action для серверного удаления
данных, а состояние диалога управлять локально через
onOpenChange.Radix UI поставляется как unstyled components, что даёт полную свободу в дизайне. Для интеграции с Remix популярны два подхода:
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded shadow-lg">
<Dialog.Title className="text-lg font-bold mb-2">Заголовок</Dialog.Title>
</Dialog.Content>
import { createStitches } from '@stitches/react';
import * as Dialog from '@radix-ui/react-dialog';
const { styled } = createStitches({
variants: {
size: {
small: { width: 300 },
large: { width: 600 },
}
}
});
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
borderRadius: '8px',
padding: '20px',
boxShadow: '0 10px 15px rgba(0,0,0,0.1)',
});
export default function MyDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>Открыть</Dialog.Trigger>
<DialogContent size="large">
<Dialog.Title>Заголовок</Dialog.Title>
</DialogContent>
</Dialog.Root>
);
}
Преимущество: лёгкая настройка вариаций компонентов через CSS-in-JS, без изменения логики компонента.
Radix UI использует ReactDOM.createPortal для рендеринга
некоторых компонентов (Popover, Dialog, Tooltip) вне обычного
DOM-дерева. В Remix это работает без дополнительной настройки, но важно
учитывать:
typeof window !== 'undefined'.Пример условного рендеринга:
import { useEffect, useState } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
export default function ClientOnlyDialog() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (
<Dialog.Root>
<Dialog.Trigger>Открыть</Dialog.Trigger>
<Dialog.Content>Контент</Dialog.Content>
</Dialog.Root>
);
}
Radix UI автоматически обрабатывает фокус и клавиатурную навигацию для всех интерактивных компонентов:
Dialog блокирует фокус внутри диалога и возвращает его
к триггеру после закрытия.Popover и DropdownMenu корректно управляют
фокусом при навигации с клавиатуры.Для Remix это особенно важно, так как серверный рендеринг не вмешивается в поведение клавиатурной навигации на клиенте.
Radix UI компоненты можно сочетать с глобальным
состоянием через useLoaderData,
useActionData или useTransition:
import * as Dialog from '@radix-ui/react-dialog';
import { useTransition } from '@remix-run/react';
export default function SubmitDialog() {
const transition = useTransition();
return (
<Dialog.Root>
<Dialog.Trigger>Отправить данные</Dialog.Trigger>
<Dialog.Content>
{transition.state === 'submitting' ? (
<p>Идёт отправка...</p>
) : (
<button type="submit">Отправить</button>
)}
</Dialog.Content>
</Dialog.Root>
);
}
Особенности:
transition.state позволяет отслеживать
процесс отправки данных в формах Remix.Компоненты, такие как Select или
DropdownMenu, удобно использовать с данными, загруженными
через loader:
import * as Sel ect from '@radix-ui/react-select';
import { useLoaderData } fr om '@remix-run/react';
export function loader() {
return [
{ id: 1, name: 'Первый элемент' },
{ id: 2, name: 'Второй элемент' },
];
}
export default function DynamicSelect() {
const options = useLoaderData();
return (
<Select.Root>
<Select.Trigger>Выберите элемент</Select.Trigger>
<Select.Content>
{options.map(option => (
<Select.Item key={option.id} value={option.id.toString()}>
<Select.ItemText>{option.name}</Select.ItemText>
</Select.Item>
))}
</Select.Content>
</Select.Root>
);
}
Remix выполняет серверный рендеринг (SSR), но Radix UI компоненты рассчитаны на клиентскую работу. Необходимо учитывать:
Portal или анимации,
должен рендериться только на клиенте.useEffect
или отдельные ClientOnly обёртки.defaultOpen или defaultValue для корректного
SSR.Эта структура обеспечивает гибкую интеграцию Radix UI в проекты Remix, с поддержкой серверного рендеринга, динамических данных, управления состоянием и кастомной стилизации.