A component kit gives you behavior and appearance, and you fight the appearance whenever your design differs from the kit's. A headless library ships only the hard half: focus management, keyboard interaction, ARIA roles and states, portals, collision-aware positioning. It renders unstyled elements carrying data attributes such as data-state="open"; you supply every class name.
import { Dialog } from 'radix-ui'; // inside a DeleteButton({ onConfirm }) component
<Dialog.Root>
<Dialog.Trigger className="btn-danger">Delete</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="modal">
<Dialog.Title>Delete this product?</Dialog.Title>
<Dialog.Description>This cannot be undone.</Dialog.Description>
<button onClick={onConfirm}>Delete</button>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>Those lines get you a focus trap, aria-modal with the title and description wired to it, Escape to close, a scroll lock, click-outside dismissal and focus returned to the trigger — a week of work if you write it yourself. Every part also accepts asChild, merging its behavior into your own element.
| Library | npm package | From | Weekly downloads |
|---|---|---|---|
| Radix 205,870 Primitives | radix-ui 1.6.7 | WorkOS 19,742 | 10.1 M |
| Base UI | @base-ui/react 1.8.0 | Radix, MUI 55,831 and Floating UI authors | 9.7 M |
| Headless UI 663,593 | @headlessui/react 2.2.10 | Tailwind 5,202 Labs | 5.4 M |
| React Aria 67 Components | react-aria-components 1.21.1 | Adobe | 3.1 M |
Radix is the safe default and the one most other projects build on; React Aria Components goes furthest on internationalization, at the cost of a larger API; Headless UI is the smallest and assumes Tailwind. shadcn/ui 77,293 belongs here but not in the table: its CLI copies Radix-based, Tailwind-styled component source files into your repository, which you then own and edit — no upgrade path, total control.