Locara

Radix UI + Headless Component Primitives

What it is: A class of component libraries that ship behavior without styling — accessibility, keyboard handling, ARIA, focus management, state machines — leaving visual presentation entirely to the consumer. The dominant 2026 examples are Radix UI (WorkOS, formerly Modulz), Headless UI (Tailwind Labs), and Ariakit (community-driven, formerly Reakit). Status: Radix is the substrate that powers shadcn/ui and most of the modern React component-library ecosystem. Headless UI is Tailwind Labs’ parallel offering. Ariakit is the most academically-rigorous, with deep WAI-ARIA compliance. All MIT-licensed, all actively maintained. Most relevant to Locara: This is the substrate layer beneath shadcn/ui, which is already covered. Locara’s @locara/components will inevitably build on something like Radix — either by depending on it, by mirroring its primitives, or by re-implementing them. Understanding the headless pattern is prerequisite to a coherent component-library decision.

Background

Until ~2019, React component libraries fell into two camps: opinionated visual libraries (Material UI, Ant Design, Chakra) that bundled behavior + styling tightly, and lower-level primitives (Reach UI, react-aria from Adobe) that were less ergonomic. Neither was great for “I want my own design system but I don’t want to re-implement keyboard nav and focus traps from scratch.”

The headless pattern emerged as a third path. Radix UI (created by Modulz, acquired by WorkOS in 2023) shipped components that exposed all the behavior — focus management, keyboard handling, ARIA roles, state, event handling — but rendered with zero styling. You provide the visuals (CSS, Tailwind, styled-components, whatever); Radix provides the behavior contract.

Tailwind Labs released Headless UI (2020) as a smaller, Tailwind-native alternative — fewer components, lighter abstraction, designed to pair with Tailwind class strings.

Ariakit (started by Diego Haz, formerly Reakit) is a third headless library with deeper WAI-ARIA compliance and a more functional/composable API surface.

By 2023, Radix had become the substrate for shadcn/ui — Brian Lovin / shadcn’s pattern was “Radix for behavior + Tailwind for styling, copied into your repo as source.” This made Radix the de facto React headless primitive layer.

Key design decisions (the headless pattern in general)

  • Behavior, not pixels. Components ship interaction logic, accessibility, focus management — but not styling. The consumer renders.
  • Compound components. A <Dialog> is composed of <Dialog.Trigger>, <Dialog.Content>, <Dialog.Close> etc., each rendering its own DOM, all coordinating via context.
  • Render props / asChild patterns. Components defer DOM rendering to the consumer. <Dialog.Trigger asChild><Button>Open</Button></Dialog.Trigger> lets you supply your own button while inheriting the trigger behavior.
  • Uncontrolled by default, controlled by opt-in. Components manage state internally unless you pass value/onValueChange to control them.
  • Accessibility is a first-class concern, not a feature. WAI-ARIA roles, keyboard support, focus traps, screen reader announcements all built in.
  • Tree-shaking-friendly. Each primitive is published as a separate npm package (@radix-ui/react-dialog, @radix-ui/react-dropdown-menu, etc.) so consumers only pull what they use.
  • Type-safe TypeScript-first. All major libraries ship .d.ts with strict types.
  • MIT-licensed, OSS-developed.

What worked

  • Radix + Tailwind became the default modern React stack. Behavior from Radix, styling from Tailwind, distribution via shadcn — the three pillars of 2024–26 component DX.
  • Solved the accessibility-vs-customization tradeoff. Pre-Radix, custom-designed component libraries usually had subpar accessibility because rolling your own focus traps and ARIA logic is hard. Radix gives you the accessibility for free; you just style.
  • The compound-component pattern scaled. Dropdown menus, dialogs, popovers, accordions, tabs — all the standard primitives have predictable APIs.
  • asChild pattern is now industry-standard. The pattern of “I want to use my own DOM element but inherit your behavior” is everywhere; Radix popularized it.
  • WAI-ARIA compliance became table-stakes. Pre-Radix, “works for keyboard users” and “works for screen readers” was something teams added later (or never). Radix made it the baseline.
  • Ecosystem flywheel. shadcn/ui → uses Radix → drives Radix usage → community contributions to Radix → more shadcn-style libraries → self-reinforcing.

What failed / criticisms

  • Steep learning curve. Compound components, asChild, render props, and context coordination are unfamiliar to React developers used to “props in, JSX out.”
  • Bundle size for unused primitives. Each Radix primitive is its own package, but real apps end up with 10–20 of them; bundle adds up.
  • Compound APIs are verbose. A <Dialog> with header, content, footer, close button is 7+ JSX elements. Power but not concise.
  • Styling discipline is on you. Radix gives behavior; you’re responsible for not making the UI look like garbage. For teams without strong design discipline, this can be worse than an opinionated library.
  • TypeScript types are dense. The flexibility (compound components, asChild, polymorphic refs) means types are complicated; error messages can be cryptic.
  • Headless UI from Tailwind Labs is narrower in scope than Radix — fewer components, less depth — but is the natural choice for Tailwind-aligned projects.
  • Ariakit has the deepest accessibility but the most complex API. Power-users love it; mainstream adoption is smaller than Radix.
  • Three competing libraries with overlapping primitives. Radix, Headless UI, Ariakit — each has its own dialog, dropdown, popover, etc. Ecosystem fragmentation.
  • WorkOS acquisition raised concerns. Radix was acquired by WorkOS in 2023; community asked about long-term governance. So far, healthy.

Specific learnings for Locara

  1. Locara’s component library should be headless under the hood, even if shadcn-style on the outside. Behavior comes from Radix (or equivalent); visuals come from Locara’s design system; distribution comes from the shadcn copy-in-source pattern. This is the proven 2026 recipe.
  2. Don’t re-implement focus traps, keyboard nav, ARIA. Depend on Radix (or Headless UI, or Ariakit). These are deep accessibility libraries built by people who do this full-time. Re-implementing means worse accessibility for years.
  3. asChild and compound-component patterns belong in @locara/components. Locara’s <Chat>, <DocDropzone>, <ToolCallTrace> should use the same patterns developers already know from Radix. Familiarity is a feature.
  4. Pin the headless dependency. Locara’s components-as-source distribution means each app copies code into its own repo. If those copies depend on @radix-ui/react-dialog, a future Radix breaking change can ripple across every Locara app. Vendor in or pin tightly.
  5. WAI-ARIA compliance is non-negotiable for the official Locara primitives. Apps that ship through Locara need real accessibility. The headless layer gives you this for free; don’t lose it.
  6. Three options in the headless space; pick one and standardize. Locara apps shouldn’t have to choose between Radix and Headless UI per-component. Pick one (Radix is the default choice in 2026 — biggest ecosystem, most components, used by shadcn) and use it everywhere.
  7. The headless pattern generalizes beyond UI. Locara’s “AI primitives” — <Chat>, <ToolCallApproval>, <ContextSelector> — should follow the same architectural pattern: behavior + state, no opinionated styling, consumer renders. This is the right shape for an AI-app SDK.
  8. Compound APIs make AI-authoring easier. When a model writes <Chat> it’s writing one element; when it writes <Dialog> with <Dialog.Trigger>, <Dialog.Content>, <Dialog.Close> it’s writing structured nesting that’s easier to reason about, not harder. The structure is self-documenting. This is a feature for LLM authoring, not a bug.
  9. WorkOS-acquired-Radix is a long-term governance concern. Tracking; if it becomes a pain point, Headless UI (Tailwind Labs, who are unlikely to be acquired hostilely) is the migration path. Don’t bet the farm on a single library; design Locara’s primitives so the headless layer is swappable.

References