Skip to main content
Juliano Alves
Back to blog

Building accessible UIs with Radix primitives

2 min read
By Juliano Alves

Radix UI ships headless components: behavior and accessibility without opinionated styles. That matches design systems where tokens live in Tailwind or CSS variables but interaction patterns (dialogs, dropdowns, tabs) must still satisfy WCAG.

Dialogs and focus management#

A modal is not “a div with position: fixed”. Keyboard users expect focus trapped inside, Escape to close, and restore focus to the trigger. @radix-ui/react-dialog wires aria-modal, role="dialog", and focus scope for you—style the overlay and panel with your stack.

Composition over configuration#

Primitives split into Root, Trigger, Content, Close. You decide layout; Radix guarantees the state machine. This reduces one-off bugs when product asks for “nested drawer inside dialog” edge cases.

Styling with Tailwind#

Use className on each part. For animations, pair with data-state attributes Radix exposes (open, closed) for enter/exit transitions without fighting display: none timing—@radix-ui/react-dialog supports forceMount + CSS when you need it.

Testing#

Pair Radix with Testing Library queries (getByRole('dialog', { name: /…/ })) instead of brittle CSS selectors. Storybook + a11y addon catches missing labels early.

Summary#

Reach for Radix (or similar primitives) when accessibility and keyboard behavior are non-negotiable. Your tokens handle look; Radix handles the interaction contract.

© 2026 Juliano Alves. All rights reserved.