Stop 13 · UI LibrariesTopics · 6/7

Radix UI

Part of the Frontend Developer Roadmap

In short

Making a dropdown that works with the keyboard, screen readers and focus is surprisingly hard. Radix does that hard part and lets you style it however you want.

A library of unstyled, fully accessible component primitives (dialogs, dropdowns, tooltips) that you style yourself — the foundation many other libraries (like shadcn/ui) are built on.

The jaadu stands in a bare flat with working switches and tap, holding a paint roller and colour swatches.

Think of it like this

A flat with plumbing and wiring done

You get a bare flat where the wiring, plumbing and doors already work. Paint, tiles and furniture are your choice. Radix primitives are that flat. Behaviour and accessibility are fitted, the look is up to you.

Watch it happen

Keyboard user and a Radix Dialog

Keyboard
Trigger
Content
Radix

Enter on Delete post

1/6The user tabs to the trigger and presses Enter.

The big ideas

What you need to know

Unstyled on purpose

Radix ships no visual styles. Use Tailwind, CSS Modules or plain CSS on each part.

Built from parts

A Dialog is Root, Trigger, Portal, Overlay, Content, Title and Close. You arrange the parts yourself.

Accessibility handled

Focus trapping, Escape to close, arrow-key menus and ARIA roles all work out of the box.

Style by state

Parts get data attributes like data-state="open", so you can animate opening and closing in CSS.

See it in code

Walk through the code

tsx
1import * as Dialog from "@radix-ui/react-dialog";2export function DeleteDialog() {3  return (4    <Dialog.Root>5      <Dialog.Trigger className="btn">Delete post</Dialog.Trigger>6      <Dialog.Portal>7        <Dialog.Overlay className="fixed inset-0 bg-black/50" />8        <Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6">9          <Dialog.Title>Delete this post?</Dialog.Title>10          <Dialog.Description>This can't be undone.</Dialog.Description>11          <Dialog.Close className="btn">Cancel</Dialog.Close>12        </Dialog.Content>13      </Dialog.Portal>14    </Dialog.Root>15  );16}

1/3Root holds the open state. Trigger is the button that opens it.

Open it and press Tab: focus stays inside. Press Escape: it closes and focus returns to the trigger. You wrote zero lines for that.

Save yourself the pain

Common mistakes

Oops

Leaving out Dialog.Title because the design has no heading.

Do this instead

Screen readers need it. Keep it and hide it visually if needed, for example with a sr-only class.

Oops

Building your own modal with a div and useState to save time.

Do this instead

You'll miss focus trapping and Escape handling. Use a primitive and style it.

Your mission

Keyboard-only test

~25 min

Quick check

Why do many teams pick Radix instead of building dropdowns and dialogs from scratch?

Go deeper

  1. 1Build a custom-styled dropdown menu using Radix's primitive components.
  2. 2Notice how much accessibility (focus trapping, ARIA) comes for free.
  3. 3Compare 'headless' libraries like Radix to fully-styled ones like Material UI.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge