
Radix UI
www.radix-ui.com- Category
- Developer Tools
- Rank
- No. 341Tools index
- Pricing
- Open Source
- Type
- TOOL
- Builder
- radix-ui
- GitHub
- 19.3k stars
- Date
About
Unstyled accessible React primitives. Headless, accessible React primitives (Dialog, Dropdown, Tooltip, etc.) that you style yourself. Powers shadcn/ui and many design systems.
What it does
Radix Primitives is a set of React building blocks for the interactive widgets the web platform never shipped properly: dialogs, selects, menus, sliders, tooltips. Each handles keyboard interaction, focus trapping, collision detection and screen reader semantics, and arrives with no visual styling whatsoever. You supply the CSS. The design rule is one component per rendered element, so refs and event handlers land where you expect, and every part is a separate export: trigger, portal, content, item, indicator. Current state is written onto the element as a data attribute, giving styling a hook.
Why it's ranked here
The engineering discipline is the reason to trust it. Continuous integration fails if any published package can neither be imported inside a React Server Component nor declare itself a client boundary, and a second static check rejects the two ref-composition patterns that caused infinite render loops under React 19. Accessibility assertions run in both unit and browser tests. Against that, the guiding-principles document leaves its documentation section as an unwritten placeholder, and it promises minimal reset styles that the same document elsewhere says are absent. The machine-checked parts are the credible parts, and they are the ones that matter.
What's good
Unstyled composes with any styling approach, so nothing has to be overridden. The umbrella package re-exports every primitive as a namespace, so you install one dependency or pick packages individually. Stateful components work controlled or uncontrolled. Experimental components carry an explicit unstable prefix, which labels the risky surface instead of hiding it. React and its companion packages are pinned to a single version across the workspace, so the tested combination is the shipped combination. MIT licensed, with versioning automated and release candidates published on every merge to the main branch.
Tradeoffs
No styles means no starting look: every visual decision, including the ones that make a menu feel finished, is yours. It is React only, and the workspace pins a single React 19 release, so older React or another framework is out. Documentation lives in a separate repository, so the tree alone will not teach you the APIs. Two components are marked unstable and can change. Splitting each widget into trigger, portal, content and item exports makes even a simple menu verbose, which is the price of getting a real element for every part.
How to use it well
Reach for this when you are building your own design system and want the interaction and accessibility layer solved rather than a look handed to you. It suits teams already on React 19 who write their own CSS and want each widget part to be a real element they can target. It does not give you a theme, tokens, a spacing scale or finished visuals, and it deliberately skips purely presentational pieces such as containers, chips and badges. If you want something that looks done on install, choose a styled kit instead.
Technical notes+
pnpm-workspace.yaml pins react, react-dom, react-is and react-test-renderer to one release across every workspace package and patches a changesets dependency. package.json is private and wires vitest, playwright against chromium, storybook and changesets, with @axe-core/playwright and vitest-axe carrying the accessibility assertions. packages/react/radix-ui/src/index.ts is a pure barrel of 35 namespace re-exports, two of them prefixed unstable_. Per-component entry points such as packages/react/select/src/index.ts and packages/react/dialog/src/index.ts open with a client directive and export both prefixed and bare part names alongside a scope factory, while packages/react/slot/src/index.ts exposes Slot, Slottable and createSlot. Two guards do the real work: scripts/rsc-compatibility.rsc.test.ts enumerates every non-private package entry point and asserts each one either imports cleanly under React's react-server condition or declares a client boundary, expecting more than 20 of them, and scripts/check-composed-refs.mjs parses package source with the TypeScript compiler API and fails the build on an inline callback passed to useComposedRefs or a render-inline composeRefs used as a ref, the crash pattern from issue #3963. AGENTS.md, philosophy.md, release-process.md and .github/CONTRIBUTING.md document the conventions, folder layout and the recreate-from-main release branch.
Observed
- License
- MIT, copyright WorkOS
- Language
- TypeScript only, with local scripts allowed to be type-checked JavaScript
- Interface
- React component library imported as JSX, no CLI
- Install surface
- npm packages, one per primitive plus an umbrella package that re-exports them all
- Styling
- Ships with no presentational styles, styling tool left entirely to the consumer
- Server rendering
- Client-only packages declare a client boundary directive so they can be imported from React Server Components
- Testing
- Unit tests with vitest, end-to-end tests with playwright on chromium, accessibility assertions via axe in both layers
- Repo layout
- pnpm workspace monorepo, versioned and published with changesets, storybook for component examples
Read from README.md, package.json, philosophy.md, AGENTS.md, LICENSE, pnpm-workspace.yaml, release-process.md, .github/CONTRIBUTING.md, packages/react/radix-ui/src/index.ts, packages/react/select/src/index.ts, packages/react/dialog/src/index.ts, packages/react/slot/src/index.ts, scripts/rsc-compatibility.rsc.test.ts, scripts/check-composed-refs.mjs.
Tags
Tech Stack
Media

Comments (0)
No comments yet
Editorially curated, with community endorsements as a secondary signal. Corrections welcome.