2026-07-21 · 11 min read · Raw Drafts / Design Systems / Frontend Architecture

The Design System Under the Design System

How Linear, Vercel, Supabase, CodeSandbox, Liveblocks, Cal.com, and Node.js use Radix Primitives, and why accessible infrastructure still creates architectural trade-offs.

Radix Primitives case studiescompanies using Radix UIRadix UI pros and consLinear design system RadixVercel Radix PrimitivesSupabase UI Radixaccessible React primitivesheadless component architecture

Linear, Vercel, Supabase, CodeSandbox, Liveblocks and Cal.com do not look like the same product.

Their typography, density, navigation, motion and visual character are noticeably different. Yet beneath those interfaces, many of them rely on the same collection of components.

Radix Primitives provides dialogs, menus, tooltips, popovers, selects, tabs, switches and other common interface patterns. But calling it a component library is slightly misleading. It does not give these companies a shared visual language. It gives them a shared behavioural language.

A Radix dialog does not decide whether it should look industrial, playful, minimal or enterprise. It decides what should happen when the user presses Escape, where keyboard focus should move, how the background should become inaccessible, how screen readers should interpret the structure and how the dialog should behave when it is nested inside other interface layers.

This makes Radix something closer to a design system under the design system.

The visible design system expresses the company. The invisible one handles the accumulated interaction knowledge of the web.

Companies are not adopting a look

Traditional component libraries often bundle appearance and behaviour together. A team installs a button, menu or modal and receives both the interaction model and somebody else's design decisions. This is convenient until the product needs to feel distinct.

Radix takes the opposite approach. Its primitives are mostly unstyled. The library provides behaviour, accessibility and state, while the product team supplies its own styling, tokens and higher-level API.

Linear uses Radix inside Orbiter, the internal design system shared across its web and desktop products. Supabase uses it as a foundation beneath Supabase UI. CodeSandbox uses it underneath its own design-system components. Liveblocks combines Radix with Tailwind in a shared system used across its dashboard, documentation and marketing surfaces. Cal.com wraps Radix packages inside its own @calcom/ui layer.

The architecture is usually similar:

Radix primitive → internal wrapper → company design system → product interface

The product rarely imports a raw primitive everywhere. Instead, the team creates a controlled wrapper around it. The wrapper applies product-specific styles, simplifies the API, limits unnecessary options and establishes how that interaction should be used throughout the company.

CodeSandbox, for example, built a standardised Context Menu abstraction on top of Radix rather than exposing every underlying option. The purpose was not merely to save code. It was to turn an open-ended primitive into a deliberate product rule.

This is an important distinction. Radix does not replace a design system. It gives a design system a more reliable behavioural foundation.

Accessibility becomes infrastructure

The most consistent reason companies give for adopting Radix is accessibility.

A menu is not simply a rectangle containing links. It needs keyboard navigation, sensible focus movement, correct ARIA attributes, typeahead behaviour, dismissal logic, support for nested items and predictable interaction across browsers and input devices.

A dialog needs focus trapping, focus restoration, Escape handling, background isolation, accessible labelling, scroll management and correct interaction with portals. A tooltip needs to distinguish keyboard focus from pointer movement and should not accidentally prevent access to the element underneath it.

These requirements are easy to underestimate because the visual result looks simple. The engineering knowledge required to make it dependable is not simple at all.

Linear says Radix significantly improved its accessibility compliance while reducing code complexity. Supabase describes Radix as removing low-level work such as Escape-key handling and dialog semantics. CodeSandbox says it spends less time debating keyboard navigation, focus traps and ARIA details. Acid Tango points specifically to dialogs, where focus lock and scroll lock previously had to be implemented manually.

Radix converts this knowledge from repeated project work into shared infrastructure.

That changes the economics of accessibility. Instead of treating it as a specialist review performed after a component has already been designed and built, accessibility becomes part of the component's starting behaviour.

This does not automatically make the entire product accessible. Teams can still misuse a primitive, provide poor labels, create weak focus styles or assemble components into confusing workflows. But the baseline is much stronger. The default question changes from "Did we remember to implement keyboard behaviour?" to "Are we composing this accessible behaviour correctly?"

That is still work, but it is a more mature kind of work.

Radix makes expensive components ordinary

The strongest benefit may be that Radix changes which components a small team can afford to build.

Basedash says interface components that previously seemed too expensive, including alerts and context menus, can be implemented in minutes and shipped within a day. Liveblocks uses Radix for dialogs, menus, context menus, scroll areas, tooltips and navigation. Its team describes being able to create custom interfaces with native-like behaviour without spending weeks reproducing the underlying mechanics.

The key saving is not the number of lines copied into the codebase. It is the number of edge cases the product team no longer has to discover independently.

Without a primitive library, the first implementation of a context menu may appear straightforward. Then the team encounters keyboard navigation, submenu positioning, viewport collisions, touch input, focus restoration, disabled items, nested portals and screen-reader semantics. The component expands from a visual task into a small interaction-engineering project.

Radix packages this hidden complexity into an interface that can be styled and composed.

This is particularly useful for products such as Linear, Supabase, CodeSandbox and Basedash because their interfaces are dense with actions. Their value depends on making complicated software feel immediate. Menus, command surfaces, popovers and layered controls are not decoration around the product. They are how the product is operated.

A marketing website can often avoid difficult interaction patterns. A professional tool cannot. Radix gives these teams a way to increase interface capability without increasing implementation risk at the same rate.

The complexity does not disappear

The optimistic story is that Radix eliminates the hard parts of interface development. The more accurate story is that it moves them.

Teams stop implementing focus traps and keyboard navigation from scratch. In exchange, they must understand composition, wrappers, context providers, portals, package versions and the semantic consequences of combining primitives with their own components.

This is most obvious in Radix's asChild pattern. A Radix trigger normally renders its own HTML element. With asChild, it can instead pass its behaviour into a child component. This allows teams to use their own buttons and links without adding unnecessary wrapper elements.

It is powerful because it separates behaviour from markup. It is also easy to misuse.

Supabase encountered a case where a dropdown trigger and an existing button produced nested button elements. Cal.com has dealt with a related problem where tooltip composition resulted in nested links. These are not failures of the visual design. The interface may look completely correct. The problem exists in the semantic structure underneath it.

Radix's flexibility therefore requires architectural discipline. Developers need to know which component owns the underlying element, which one is forwarding a ref and which behaviours are being merged into the child.

Context-based primitives introduce another category of problem. Cal.com has documented errors caused by tooltips being rendered outside the required Tooltip Provider. Once a primitive becomes deeply wrapped inside a company design system, the source of this kind of failure can be several abstraction layers away from the visible component.

Package modularity also has two sides. Linear, Vercel and Node.js appreciate that individual primitives can be installed separately, helping teams control their dependency footprint. Yet a large application may end up with many closely related Radix packages that need compatible versions. Framework transitions can expose that dependency chain. Node.js tracked Radix compatibility during its React 19 migration, while its team has also expressed interest in stronger React Server Components support.

Radix Select appears to be a recurring pressure point. Linear mentioned needing assistance and workarounds during its implementation. Across the broader Radix issue tracker, developers have reported difficulties involving searchable selects, very large option sets, form resets, positioning and integration inside other layered components.

This does not mean Radix Select is bad. Select is one of the hardest interface components to generalise. A native select, autocomplete, command menu and searchable database picker may look related, but they have different interaction models. A primitive can cover a strong default model without becoming the correct answer for every product-specific variation.

The downside of adopting a mature primitive is that its model becomes part of the product's architecture. When the product eventually needs behaviour outside that model, the team must decide whether to extend it, work around it or replace it.

Unstyled does not mean effortless

Radix is often described as highly customisable because it is unstyled. That is true, but unstyled components do not remove design work. They expose it.

A fully styled component library answers many questions in advance: spacing, typography, elevation, colours, animation, responsive behaviour and visual states. Radix leaves most of these decisions to the product team.

This freedom is precisely why companies such as Linear and Vercel can use the same primitives without looking alike. It is also why Radix works best when a team already has, or intends to build, a serious design system.

The primitives still need wrappers. Those wrappers need tokens, variants, documentation, testing and rules. Teams need to decide whether a dialog should have a standard width, how dropdown items should be spaced, which animations are acceptable, how destructive actions should appear and whether product engineers are allowed to access the underlying Radix API directly.

The University of Amsterdam praised Radix for its customisability and TypeScript ergonomics but noted that the documentation did not always cover the practical patterns its team needed. This is a predictable tension in low-level libraries. The more general the primitive, the less confidently its documentation can prescribe the architecture of the system built above it.

Radix gives a team fewer visual constraints, but that means the team must create better internal constraints.

The real product is the wrapper layer

When companies describe how they use Radix, the most important part is often not Radix itself. It is the layer they construct on top.

Linear has Orbiter. Supabase has Supabase UI. Cal.com has @calcom/ui. Node.js uses Radix inside its web design system. These systems translate generic primitives into company-specific product language.

That wrapper layer is where behaviour becomes policy.

A raw Dialog primitive can support many arrangements. The company Dialog may enforce a title, description, close button, width system and approved footer structure. A raw Context Menu can expose many capabilities. The company Context Menu may limit those capabilities to prevent inconsistent interactions. A raw Tooltip can technically surround many elements. The company Tooltip may establish rules about when tooltips are appropriate and how long their content can be.

The primitive solves mechanical correctness. The wrapper solves organisational consistency.

This is also why directly importing Radix throughout an application can create long-term problems. It allows every developer to make local composition decisions. Over time, the product accumulates slightly different dialogs, menus and triggers that share a dependency but not a coherent usage model.

The better implementation is not "we use Radix." It is "we have decided how our product uses Radix."

Radix is most valuable when it becomes invisible.

A new division of interface labour

The rise of Radix reflects a broader change in frontend development. Product teams increasingly separate three things that older component libraries often combined:

Behaviour comes from primitives.

Appearance comes from the design system.

Composition comes from the product.

This division is visible in shadcn/ui as well. Its early popularity helped expose Radix to a much larger audience by combining primitive behaviour with editable component code and Tailwind styling. But the companies using Radix most deeply are not merely installing a fashionable stack. They are using primitives to decide which layers of interface engineering should remain proprietary.

Linear should own the interaction details that make Linear distinctive. It probably does not gain a competitive advantage from independently rediscovering how focus should return after a dialog closes.

Supabase should own the workflows for managing databases and infrastructure. It does not need to create a new interpretation of keyboard navigation for every menu.

Radix allows teams to standardise the parts of interaction design that should be dependable while customising the parts that should be expressive.

That is the real trade.

The benefit is not that interface development becomes easy. The benefit is that a company can spend more of its difficulty on the product itself.

The cost is that the team must understand the boundary. Accessibility primitives can be composed incorrectly. Unstyled components still require a visual system. Flexible APIs still need internal restrictions. Shared behaviour still has to be translated into product rules.

Radix does not remove interface complexity.

It moves complexity away from repeatedly rebuilding individual components and toward designing the system that connects them.

For teams capable of maintaining that system, this is a very good deal.

Connected reading

AI authorship note

This essay grew from Kris Haamer asking which products use Radix Primitives, then requesting deeper research into how those companies use the library and what trade-offs they report. AI supported source discovery, comparison, drafting, editing, and implementation. Kris directed the article toward the architectural idea of a design system beneath the visible design system and chose the final version for publication.

Help improve this website?

Optional Google Analytics and Microsoft Clarity measure content performance and usability. They load only if you allow them. Form values, email addresses, and chat messages are never included in analytics events.