Radix UI предоставляет низкоуровневые, доступные компоненты для React, которые можно использовать как строительные блоки для сложных интерфейсов. В основе всех компонентов лежит контролируемое состояние и набор событий, что позволяет переопределять стандартное поведение без нарушения доступности и логики компонента.
Большинство компонентов Radix UI поддерживают контролируемый и неконтролируемый режим.
Accordion открывает и закрывает панели без участия внешнего
состояния.value и колбэки
onValueChange. Это позволяет внедрять собственную логику
при изменении состояния.Пример контролируемого Accordion:
import * as Accordion from '@radix-ui/react-accordion';
import { useState } from 'react';
export default function CustomAccordion() {
const [value, setValue] = useState('item-1');
return (
<Accordion.Root value={value} onValueCha nge={setValue}>
<Accordion.Item value="item-1">
<Accordion.Header>
<Accordion.Trigger>Пункт 1</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Содержимое 1</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="item-2">
<Accordion.Header>
<Accordion.Trigger>Пункт 2</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Содержимое 2</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
}
В этом примере можно полностью контролировать, какая панель открыта,
переопределяя поведение через setValue.
Radix UI компоненты используют event forwarding, позволяя подключать свои обработчики к стандартным событиям.
onClick, onKeyDown,
onPointerDown и другие можно использовать одновременно с
внутренней логикой Radix.event.preventDefault() и
event.stopPropagation(), но нужно учитывать, что Radix
часто использует useControllableState и собственные
обработчики, которые могут срабатывать после вашего кода.Пример кастомного закрытия Dialog по нажатию на кнопку с
дополнительной логикой:
import * as Dialog from '@radix-ui/react-dialog';
function CustomDialog() {
const [open, setOpen] = useState(false);
const handleClose = () => {
console.log('Диалог закрыт');
setOpen(false);
};
return (
<Dialog.Root open={open} onOpenCha nge={setOpen}>
<Dialog.Trigger>Открыть диалог</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Заголовок</Dialog.Title>
<Dialog.Description>Описание диалога</Dialog.Description>
<button onCl ick={handleClose}>Закрыть с логикой</button>
</Dialog.Content>
</Dialog.Root>
);
}
Здесь стандартное поведение Dialog по закрытию через
Trigger остаётся доступным, но добавляется пользовательская
логика при закрытии через отдельную кнопку.
asChildRadix UI компоненты предоставляют проп asChild,
позволяющий использовать любой HTML-элемент или кастомный компонент
вместо стандартного рендера. Это особенно полезно для переопределения
визуального поведения и поведения событий.
Пример с Button:
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
export default function CustomDropdown() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<a href="#" onCl ick={(e) => e.preventDefault()}>Меню</a>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item onSel ect={() => console.log('Выбран пункт 1')}>Пункт 1</DropdownMenu.Item>
<DropdownMenu.Item onSel ect={() => console.log('Выбран пункт 2')}>Пункт 2</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
Использование asChild позволяет интегрировать компоненты
Radix в существующий дизайн, сохраняя при этом логику доступности и
управление состоянием.
onOpenChange и
onValueChangeRadix UI предоставляет централизованные колбэки для отслеживания состояния:
Dialog, Popover, DropdownMenu для
отслеживания открытия/закрытия.Tabs,
Accordion, RadioGroup для управления
выбором.Эти колбэки позволяют внедрять:
Пример динамической блокировки переключения вкладок:
import * as Tabs from '@radix-ui/react-tabs';
import { useState } from 'react';
export default function ControlledTabs() {
const [tab, setTab] = useState('tab1');
const handleTabChange = (newTab) => {
if (newTab !== 'tab2') {
setTab(newTab);
} else {
alert('Вкладка 2 недоступна');
}
};
return (
<Tabs.Root value={tab} onValueCha nge={handleTabChange}>
<Tabs.List>
<Tabs.Trigger value="tab1">Вкладка 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Вкладка 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Контент 1</Tabs.Content>
<Tabs.Content value="tab2">Контент 2</Tabs.Content>
</Tabs.Root>
);
}
При переопределении стандартного поведения необходимо учитывать:
value и колбэки вместо прямого манипулирования
DOM.Radix UI активно применяет контексты и слоты для передачи логики внутренним компонентам:
Accordion, Tabs,
RadioGroup) без передачи состояния через пропсы вниз по
дереву.Это позволяет переопределять отдельные части поведения, например, добавлять кастомные обработчики на конкретные элементы, сохраняя при этом согласованность состояния всего компонента.
Эти подходы делают Radix UI гибкой платформой для построения сложных интерфейсов, где можно полностью контролировать пользовательский опыт, не нарушая встроенных стандартов доступности и логики компонентов.