Headless UI Components Explained: Logic Without Locked Styling

Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.

6 min read

Headless UI components split behavior from presentation. A headless component handles the accessible logic, keyboard interaction, and state of a widget, and ships no visual styling, so you build the look yourself. Radix Primitives is the canonical example.

Behavior without a look

A styled component library gives you a finished button or dialog. A headless library gives you the working dialog logic and lets you paint every part. The tradeoff is more markup up front, in exchange for a widget that matches your design system exactly.

That separation matters because accessibility is the hard half. Focus management, ARIA wiring, and keyboard behavior are easy to get wrong and tedious to rebuild, while a rounded corner or a shadow is easy to write yourself.

How a headless component works

Radix Primitives ship as the radix-ui package. Install it and import the widgets you need.

bashbash
npm install radix-ui

Each primitive exposes a small tree of parts. A tooltip, for example, has a trigger and floating content, and Radix handles the hover, focus, and positioning behavior between them.

App.jsxApp.jsx
import { Tooltip } from "radix-ui";
 
<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger asChild>
      <button>Save</button>
    </Tooltip.Trigger>
    <Tooltip.Portal>
      <Tooltip.Content>Save your changes</Tooltip.Content>
    </Tooltip.Portal>
  </Tooltip.Root>
</Tooltip.Provider>

None of these parts carry a color or font. The asChild prop renders the trigger through your own button, so you keep full control of the element while Radix keeps the tooltip behavior and accessibility.

Style it yourself

Styling is just className on each part. The same tooltip with Tailwind utilities looks like this.

App.jsxApp.jsx
<Tooltip.Content className="rounded bg-gray-900 px-2 py-1 text-xs text-white">
  Save your changes
</Tooltip.Content>

The gray background and padding are entirely yours. Radix still positions the content next to the trigger, dismisses it on Escape and blur, and adds the correct ARIA relationships, none of which you had to reimplement.

Why this beats rebuilding widgets

Building a dialog from scratch means focus trapping, ARIA, and keyboard handling, which is exactly the work a headless library already did and tested. Using one removes that entire category of bugs from your components.

When to use headless components

Reach for a headless library when you need a complex widget and your team wants a custom look. A dialog, menu, or select is exactly the kind of component where the accessibility is hard and the styling is easy to own. The library ships the hard part, and you keep the visual part.

Skip it for simple elements. A plain button or input already carries the behavior and semantics, and wrapping it in a primitive adds a dependency without benefit.

The primitive's internal structure is the compound components pattern at scale, and the hand built version of one such widget is the accessible tooltip walkthrough.

Rune AI

Rune AI

Key Insights

  • Headless components ship behavior without styles.
  • Radix Primitives is the canonical unstyled component library.
  • Install the radix-ui package and import the primitives you need.
  • Style each part with your own className values.
  • Use asChild to render a primitive through your own element.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between headless and styled component libraries?

A styled library ships prebuilt visual components. A headless library ships only the behavior, keyboard handling, and ARIA wiring, and you supply the markup and styles.

Do headless components work with Tailwind?

Yes. Because they ship without styles, you attach your own className values to their parts, which makes them a natural fit for Tailwind or any styling approach.

Is Radix the only headless library?

No. Radix Primitives is the most widely used, but the same idea appears in libraries such as Headless UI and in the unstyled parts of shadcn/ui.

Conclusion

Headless UI components separate behavior from presentation. They handle ARIA, focus, and keyboard interactions while leaving every visual decision to you, which is why libraries like Radix form the base of many design systems.