Star Forge components are built on top of headless UI primitives from either Radix UI or Base UI. The architecture uses a file-level adapter pattern: each primitive lives in two parallel folders with identical public APIs.
The choice between Radix UI and Base UI is made at install time, not at runtime. There is no engine detection logic in any component.
Barrel files in src/components/ui/*.tsx re-export from primitives/radix/ by default. This preserves backward compatibility: existing blocks continue to work unchanged.
To switch a primitive to Base UI, change the barrel file:
// src/components/ui/select.tsx// export * from './primitives/radix/select'; // Radix (default)export * from './primitives/base/select'; // Base UI
Base UI does not provide a Slot primitive. src/components/ui/_lib/slot-polyfill.tsx implements asChild behavior using mergeProps + cloneElement, enabling Button and Badge to support asChild in both engines.
Tabs, TabsContent, TabsList, TabsTrigger, TabsIndicator (bonus in Base UI)
Tabs Indicator is exported by both variants, but only Base UI has a real implementation. The Radix variant exports an empty placeholder to keep imports identical.
Base UI does not expose a standalone Anchor component. The Base UI adapter uses a native <div> with the same data-slot="popover-anchor", preserving the public API.
Base UI Select defaults to aligning the selected item with the trigger text. In the Base UI adapter, position="item-aligned" maps to alignItemWithTrigger on the internal Positioner.
Radix UI uses @radix-ui/react-slot. Base UI uses the custom Slot polyfill in _lib/slot-polyfill.tsx. Both support asChild with identical behavior for single-element children.