Root и Indicator

Root — это базовый контейнер любого составного компонента в библиотеке Radix UI. Он выступает точкой инициализации состояния, логики и контекста, который затем используется дочерними элементами. Практически каждый компонент Radix UI строится вокруг этого корневого элемента.

Root выполняет несколько ключевых функций:

  • хранение состояния компонента;
  • управление доступностью (ARIA-атрибуты);
  • предоставление контекста для дочерних компонентов;
  • контроль поведения и событий;
  • интеграция с управляемыми и неуправляемыми режимами.

В архитектуре Radix UI компоненты разделяются на несколько частей. Root — это всегда центральный элемент этой структуры.

Пример типичной структуры компонента:

<Component.Root>
  <Component.Trigger />
  <Component.Content />
</Component.Root>

В этой конструкции Root инициализирует состояние и распространяет его на Trigger и Content через внутренний контекст React.


Архитектурная роль Root

Radix UI придерживается принципа compound components — составных компонентов. Root является их основой.

Внутри Root происходят следующие процессы:

  1. Создание React-контекста
  2. Управление состоянием
  3. Регистрация дочерних элементов
  4. Передача данных дочерним компонентам
  5. Управление доступностью

Упрощённая концептуальная схема:

Root
 ├─ хранит state
 ├─ создаёт Context
 ├─ передаёт Context
 │
 ├── Trigger
 ├── Content
 └── Indicator

Все дочерние элементы автоматически получают доступ к состоянию через внутренний Context.


Root как источник состояния

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 в этом случае выступает посредником между внешним состоянием и внутренними элементами.


Основные свойства Root

value

Текущее значение компонента (в управляемом режиме).

value="tab1"

Используется для синхронизации состояния.


defaultValue

Начальное значение (неуправляемый режим).

defaultValue="tab1"

После инициализации состояние управляется самим компонентом.


onValueChange

Колбэк, вызываемый при изменении значения.

onValueCha nge={(value) => console.log(value)}

Позволяет реагировать на действия пользователя.


orientation

Используется в компонентах, где имеет значение направление.

orientation="horizontal"
orientation="vertical"

Пример использования в Tabs:

<Tabs.Root orientation="vertical">

dir

Направление текста:

dir="ltr"
dir="rtl"

Необходимо для корректной работы интерфейсов с правосторонними языками.


Пример внутренней реализации Root

Внутри библиотеки 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>
  );
});

Реальная реализация значительно сложнее и включает:

  • управление фокусом
  • обработку клавиатуры
  • ARIA-атрибуты
  • управление порталом
  • синхронизацию DOM

Root и управление доступностью

Radix UI уделяет большое внимание accessibility (a11y). Root автоматически управляет:

  • ролями ARIA
  • состояниями aria-selected
  • состояниями aria-expanded
  • связями aria-controls

Пример автоматически генерируемых атрибутов:

role="tablist"
aria-orientation="horizontal"

Это избавляет от необходимости вручную реализовывать сложные механизмы доступности.


Передача состояния через Context

Root создаёт React Context, который используется дочерними компонентами.

Пример логики:

const Context = React.createContext();

<Context.Provider value={state}>

Дочерние компоненты получают состояние через:

const context = useContext(Context);

Таким образом:

  • Trigger знает активный элемент
  • Content знает, когда отображаться
  • Indicator знает позицию активного элемента

Компонент Indicator

Indicator — вспомогательный элемент, который отображает визуальное состояние выбора внутри составного компонента.

Он используется для индикации:

  • активной вкладки
  • выбранного пункта
  • текущей позиции
  • состояния переключения

Indicator не управляет логикой. Его задача — визуально отражать состояние Root.


Назначение Indicator

Indicator используется в следующих компонентах Radix UI:

  • Tabs
  • Navigation Menu
  • Select
  • Radio Group
  • Toggle Group

Он выполняет роль динамического маркера состояния.

Примеры визуальных элементов:

  • подчёркивание активной вкладки
  • перемещающаяся линия
  • маркер выбора
  • индикатор переключателя

Пример использования Indicator

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

Indicator подписывается на контекст Root и получает:

  • активное значение
  • положение элемента
  • размеры

Далее происходит:

  1. Определение активного элемента
  2. Получение его DOM-координат
  3. Позиционирование Indicator
  4. Анимация перехода

Упрощённая логика:

activeTrigger = findTrigger(value)

position = activeTrigger.getBoundingClientRect()

indicator.style.left = position.left
indicator.style.width = position.width

Indicator и анимации

Indicator часто используется вместе с CSS-анимациями.

Пример:

.indicator {
  height: 2px;
  background: black;
  transition: transform 200ms ease;
}

При переключении вкладок позиция индикатора плавно изменяется.


Indicator в Navigation Menu

Один из наиболее сложных вариантов использования — Navigation Menu.

Пример:

<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
    </NavigationMenu.Item>

    <NavigationMenu.Indicator />
  </NavigationMenu.List>
</NavigationMenu.Root>

Indicator может:

  • перемещаться между пунктами
  • изменять размер
  • анимироваться

Indicator и позиционирование

Indicator может использовать различные стратегии позиционирования.

Абсолютное позиционирование

position: absolute;
bottom: 0;

Используется для линий под вкладками.


Центрирование

transform: translateX(-50%);
left: 50%;

Подходит для круглых индикаторов.


Следование за элементом

Indicator может динамически менять:

  • width
  • height
  • transform

Это создаёт эффект перемещения.


Доступность Indicator

Indicator не влияет на доступность напрямую, так как он является чисто визуальным элементом.

Поэтому он:

  • не содержит интерактивности
  • не имеет ARIA-ролей
  • не участвует в навигации

Его основная задача — улучшение визуального восприятия интерфейса.


Кастомизация 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

При использовании Indicator важно избегать layout shift — изменения разметки.

Поэтому чаще всего применяется:

position: absolute

Indicator располагается поверх элементов и не влияет на поток документа.


Связь Root и Indicator

Root и Indicator тесно взаимодействуют через контекст.

Root предоставляет:

  • текущее значение
  • список элементов
  • размеры

Indicator использует эти данные для:

  • вычисления позиции
  • отображения состояния

Схема взаимодействия:

Root
 ├─ state (value)
 ├─ context
 │
 ├── Trigger
 ├── Content
 └── Indicator
        │
        └─ читает state
           вычисляет позицию
           отображает маркер

Внутренние оптимизации

Radix UI оптимизирует работу Indicator с помощью:

  • ResizeObserver
  • MutationObserver
  • requestAnimationFrame

Это позволяет:

  • корректно реагировать на изменение размеров
  • избегать лишних перерисовок
  • обеспечивать плавную анимацию

Типичная структура Tabs с Indicator

<Tabs.Root>
  <Tabs.List>
    <Tabs.Trigger />
    <Tabs.Trigger />
    <Tabs.Trigger />

    <Tabs.Indicator />
  </Tabs.List>

  <Tabs.Content />
  <Tabs.Content />
  <Tabs.Content />
</Tabs.Root>

Здесь:

  • Root управляет состоянием
  • Trigger изменяет состояние
  • Content отображает данные
  • Indicator визуализирует активный элемент

Преимущества архитектуры Root + Indicator

1. Чёткое разделение ответственности

Root — логика Indicator — визуальное состояние


2. Гибкость

Можно полностью изменить внешний вид Indicator без изменения логики.


3. Переиспользуемость

Indicator можно применять в разных компонентах.


4. Производительность

Root централизует состояние и минимизирует лишние обновления.


5. Расширяемость

Новые элементы могут подключаться к контексту Root без изменения существующей структуры.


Такая архитектура делает Radix UI мощным инструментом для построения сложных интерфейсов с высокой степенью контроля, доступности и предсказуемости поведения компонентов.