Root — это базовый контейнер любого составного компонента в библиотеке Radix UI. Он выступает точкой инициализации состояния, логики и контекста, который затем используется дочерними элементами. Практически каждый компонент Radix UI строится вокруг этого корневого элемента.
Root выполняет несколько ключевых функций:
В архитектуре Radix UI компоненты разделяются на несколько частей. Root — это всегда центральный элемент этой структуры.
Пример типичной структуры компонента:
<Component.Root>
<Component.Trigger />
<Component.Content />
</Component.Root>
В этой конструкции Root инициализирует состояние и распространяет его на Trigger и Content через внутренний контекст React.
Radix UI придерживается принципа compound components — составных компонентов. Root является их основой.
Внутри Root происходят следующие процессы:
Упрощённая концептуальная схема:
Root
├─ хранит state
├─ создаёт Context
├─ передаёт Context
│
├── Trigger
├── Content
└── Indicator
Все дочерние элементы автоматически получают доступ к состоянию через внутренний Context.
Root может работать в двух режимах:
Состояние хранится внутри компонента.
Пример:
import * as Tabs from "@radix-ui/react-tabs";
<Tabs.Root defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs.Root>
Здесь defaultValue задаёт начальное состояние, а
управление остаётся внутри Root.
Состояние контролируется внешним React-компонентом.
const [tab, setTab] = useState("tab1");
<Tabs.Root value={tab} onValueCha nge={setTab}>
Root в этом случае выступает посредником между внешним состоянием и внутренними элементами.
Текущее значение компонента (в управляемом режиме).
value="tab1"
Используется для синхронизации состояния.
Начальное значение (неуправляемый режим).
defaultValue="tab1"
После инициализации состояние управляется самим компонентом.
Колбэк, вызываемый при изменении значения.
onValueCha nge={(value) => console.log(value)}
Позволяет реагировать на действия пользователя.
Используется в компонентах, где имеет значение направление.
orientation="horizontal"
orientation="vertical"
Пример использования в Tabs:
<Tabs.Root orientation="vertical">
Направление текста:
dir="ltr"
dir="rtl"
Необходимо для корректной работы интерфейсов с правосторонними языками.
Внутри библиотеки Root примерно выполняет следующие действия:
const Root = React.forwardRef((props, ref) => {
const [value, setValue] = useState(props.defaultValue);
const context = {
value,
onChange: setValue
};
return (
<Context.Provider value={context}>
<div ref={ref}>{props.children}</div>
</Context.Provider>
);
});
Реальная реализация значительно сложнее и включает:
Radix UI уделяет большое внимание accessibility (a11y). Root автоматически управляет:
aria-selectedaria-expandedaria-controlsПример автоматически генерируемых атрибутов:
role="tablist"
aria-orientation="horizontal"
Это избавляет от необходимости вручную реализовывать сложные механизмы доступности.
Root создаёт React Context, который используется дочерними компонентами.
Пример логики:
const Context = React.createContext();
<Context.Provider value={state}>
Дочерние компоненты получают состояние через:
const context = useContext(Context);
Таким образом:
Indicator — вспомогательный элемент, который отображает визуальное состояние выбора внутри составного компонента.
Он используется для индикации:
Indicator не управляет логикой. Его задача — визуально отражать состояние Root.
Indicator используется в следующих компонентах Radix UI:
Он выполняет роль динамического маркера состояния.
Примеры визуальных элементов:
import * as Tabs from "@radix-ui/react-tabs";
<Tabs.Root defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
</Tabs.Root>
Indicator автоматически реагирует на изменение активной вкладки.
Indicator подписывается на контекст Root и получает:
Далее происходит:
Упрощённая логика:
activeTrigger = findTrigger(value)
position = activeTrigger.getBoundingClientRect()
indicator.style.left = position.left
indicator.style.width = position.width
Indicator часто используется вместе с CSS-анимациями.
Пример:
.indicator {
height: 2px;
background: black;
transition: transform 200ms ease;
}
При переключении вкладок позиция индикатора плавно изменяется.
Один из наиболее сложных вариантов использования — Navigation Menu.
Пример:
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
</NavigationMenu.Item>
<NavigationMenu.Indicator />
</NavigationMenu.List>
</NavigationMenu.Root>
Indicator может:
Indicator может использовать различные стратегии позиционирования.
position: absolute;
bottom: 0;
Используется для линий под вкладками.
transform: translateX(-50%);
left: 50%;
Подходит для круглых индикаторов.
Indicator может динамически менять:
widthheighttransformЭто создаёт эффект перемещения.
Indicator не влияет на доступность напрямую, так как он является чисто визуальным элементом.
Поэтому он:
Его основная задача — улучшение визуального восприятия интерфейса.
Radix UI позволяет полностью контролировать внешний вид Indicator.
Пример кастомного индикатора:
<Tabs.Indicator className="tabs-indicator" />
CSS:
.tabs-indicator {
position: absolute;
bottom: 0;
height: 3px;
background: blue;
transition: all 250ms ease;
}
При использовании Indicator важно избегать layout shift — изменения разметки.
Поэтому чаще всего применяется:
position: absolute
Indicator располагается поверх элементов и не влияет на поток документа.
Root и Indicator тесно взаимодействуют через контекст.
Root предоставляет:
Indicator использует эти данные для:
Схема взаимодействия:
Root
├─ state (value)
├─ context
│
├── Trigger
├── Content
└── Indicator
│
└─ читает state
вычисляет позицию
отображает маркер
Radix UI оптимизирует работу Indicator с помощью:
ResizeObserverMutationObserverrequestAnimationFrameЭто позволяет:
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger />
<Tabs.Trigger />
<Tabs.Trigger />
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content />
<Tabs.Content />
<Tabs.Content />
</Tabs.Root>
Здесь:
1. Чёткое разделение ответственности
Root — логика Indicator — визуальное состояние
2. Гибкость
Можно полностью изменить внешний вид Indicator без изменения логики.
3. Переиспользуемость
Indicator можно применять в разных компонентах.
4. Производительность
Root централизует состояние и минимизирует лишние обновления.
5. Расширяемость
Новые элементы могут подключаться к контексту Root без изменения существующей структуры.
Такая архитектура делает Radix UI мощным инструментом для построения сложных интерфейсов с высокой степенью контроля, доступности и предсказуемости поведения компонентов.