@octanejs/radix
v0.1.69
Published
Radix UI Primitives for the octane renderer — a port of @radix-ui/react (headless, accessible UI primitives) on octane's hooks, built on the same @octanejs/floating-ui substrate.
Keywords
Readme
@octanejs/radix
Radix UI Primitives for the
octane UI framework — a port of
@radix-ui/react (headless, accessible UI
primitives) on octane's hooks. It mirrors the unified radix-ui package's shape: the
low-level composition utilities are exported directly, and each component is a namespace
(Separator.Root, Label.Root).
Installation
npm install @octanejs/radix
pnpm add @octanejs/radiximport { Separator, Label } from '@octanejs/radix';
function Field() {
return (
<div>
<Label.Root class="label">Email</Label.Root>
<Separator.Root orientation="horizontal" />
</div>
);
}How it works
The React binding layer maps onto octane cleanly: forwardRef → octane's ref-as-prop,
composeRefs → composeRefs/useComposedRefs, and Radix's Slot/asChild
(Children.only + cloneElement + prop/ref merge) → octane's runtime Children /
cloneElement / isValidElement. Popper/focus/dismiss behavior reuses the already-ported
@octanejs/floating-ui. See
docs/radix-migration-plan.md for the full plan.
asChild takes a descriptor
Slot/asChild operate on element descriptors. In .tsrx/.tsx, prop-position JSX
(el={<button/>}), createElement, and .map() returns are descriptors, but
children-position JSX compiles to a render function. So an octane asChild consumer passes
the child element at a value/prop position (or via createElement) rather than React's
children-position <Trigger asChild><button/></Trigger>.
Status
Surface-present against the unified [email protected] component exports. Landed:
- Composition foundation —
Slot,Slottable,Primitive.<tag>(asChild),mergeProps(event chaining,stylemerge, clsx-styleclasscomposition),composeRefs/useComposedRefs,composeEventHandlers. - State foundation —
useControllableState(controlled/uncontrolled),Presence(keeps a child mounted through its CSS exit animation), the fullcreateContextScope(createScope+composeContextScopes) so composed primitives can't collide,createCollection(the Collection primitive:data-radix-collection-itemstamping + DOM-ordered item registry), theradix--prefixeduseId,useCallbackRef,Direction(useDirection/Provider), anduseSize. - Components —
Separator,Label,Collapsible,Accordion(single + multiple, full keyboard nav),Dialog(modal + non-modal),AlertDialog,Toggle,ToggleGroup,Tabs,Toolbar(roving focus + embedded ToggleGroup),AspectRatio,VisuallyHidden,Avatar(image loading state machine),Progress,Arrow— the roving-focus family on the fullRovingFocusGroupport. - The Popper overlay family —
Popper(anchor/content/arrow on@octanejs/floating-ui's positioning core: offset/shift/flip/size/arrow/hide + transform-origin middleware,--radix-popper-*CSS vars, virtual anchors),Tooltip(provider delays, skip-delay, grace-area convex hull, hidden a11y copy),Popover(modal + non-modal, custom Anchor),HoverCard(open/close delays, selection containment), the sharedMenuprimitive (typeahead, checkbox/radio items + indicators, submenus with pointer-grace polygons),DropdownMenu, andContextMenu(right-click / long-press virtual anchor). ScrollArea— custom scrollbars over a native scroll viewport: all four visibility strategies (hover/scrollstate machine /autooverflow measurement /always), thumb drag + wheel scroll geometry, corner, and the scroll-linked-effect-avoiding rAF thumb loop.- The form batch —
Checkbox(indeterminate),Switch,RadioGroup(roving-focus arrow keys check items),Slider(multi-thumb, keyboard + pointer-capture sliding), andForm(native Constraint Validation: built-in + custom sync/async matchers, messages wired intoaria-describedby). Each control renders Radix's hidden native "bubble input" inside forms, so FormData, form reset, and<form onChange>reflect state natively (see the per-file headers for the documented octane adaptations around the uncontrolled-input model). - The final six —
Menubar(menu family over the shared Menu primitive),Select(item-aligned + popper positioning, typeahead, hidden native bubble<select>),NavigationMenu(viewport/indicator machinery, motion attributes, delayed open/close),Toast(viewport hotkey, pausable timers, swipe machinery, announce regions),OneTimePasswordField(per-char cells, paste distribution, roving focus), andPasswordToggleField— plusAccessibleIcon,useEffectEvent, anduseIsHydrated. - Overlay infra —
Portal,DismissableLayer,FocusScope,useFocusGuards,useScrollLock(a focusedreact-remove-scrollreplacement — seescroll-lock.ts), with the framework-agnosticaria-hiddenpackage reused as-is.
Bounded evidence against real Radix: the differential suite
(tests/differential/parity.test.ts) runs the SAME fixture through @octanejs/radix and
the real radix-ui package on React, asserting byte-identical DOM after every interaction
step; portal'd overlays (which the rig can't see) carry dedicated focus/keyboard/dismiss
unit suites. The exact transitive source and test boundary is authenticated in
UPSTREAM.md. The 16-case differential is not exhaustive verification of the
complete canonical suite, so status remains recorded-unverified.
Remaining (documented in
docs/radix-migration-plan.md): SSR/hydration
coverage for the overlay/portal components and Phase-5 polish. The port surfaced —
and fixed, in octane itself — fourteen runtime/compiler parity bugs along the way;
each is pinned by an octane regression test and a changeset.
Status
Current scope, known divergences, and verification status are tracked in the
generated bindings status table, sourced from
this package's status.json.
