Skip to content
Created: April 19, 2025

1. What is Radix UI?Understand in one sentence ​

  • Radix UI = Headless, accessibility-first React interactive component primitives

It provides:

  • ✅ Behavioral logic (open / close / focus)
  • ✅ Keyboard interaction
  • ✅ ARIA Properties
  • ✅ Status management
  • ❌ No styles provided

👉 It is the "interaction layer", not the "UI layer"

2. Why is Radix UI important? ​

If you write a Dialog yourself, you need to deal with:

  • ESC close
  • focus trap
  • Tab loop
  • aria-modal
  • screen reader
  • Portal

👉 Radix does it all for you, and WAI-ARIA compliant

3. Core design philosophy of Radix UI ​

1️⃣ Headless (no style) ​

tsx
<DialogContent className="bg-white rounded" /-

Styling is completely under your control

2️⃣ Compound Components ​

tsx
<Dialog>
  <DialogTrigger />
  <DialogContent />
</Dialog>
  • The parent component provides context
  • Subcomponent consumption context
  • Strong constraints and strong semantics

3️⃣ Status controllable / uncontrollable ​

tsx
<Dialog open={open} onOpenChange={setOpen} />

Or:

tsx
<Dialog defaultOpen />

4️⃣ Accessibility First (not an afterthought) ​

Every API at Radix is designed around accessibility.

4. Radix UI component classification ​

Overlay class (most important) ​

  • Dialog
  • AlertDialog
  • Popover
  • Tooltip
  • DropdownMenu
  • ContextMenu

👉 90% of interactions come from here

Form & Input ​

  • Checkbox
  • RadioGroup
  • Switch
  • Slider
  • Select
  • Tabs
  • Accordion
  • NavigationMenu

5. Core Mechanism 1: Trigger/Content ​

tsx
<Popover>
  <PopoverTrigger>Open</PopoverTrigger>
  <PopoverContent>Content</PopoverContent>
</Popover>

Inside Radix:

  • Trigger control open
  • Content response open
  • State is shared via context

6. asChild (very important) ​

Why asChild? ​

tsx
<DialogTrigger>
  <Button />
</DialogTrigger>

❌ Will wrap an extra layer of DOM

Correct way ​

tsx
<DialogTrigger asChild>
  <Button />
</DialogTrigger>

👉 No new DOM, only inject behavior

The bottom layer uses Slot

7. Portal (solve hierarchical issues) ​

tsx
<DialogPortal>
  <DialogContent />
</DialogPortal>
  • Render to body
  • Avoid z-index hell

8. Radix’s State & Data Attributes ​

Radix will automatically inject:

html
data-state="open"
data-disabled
data-side="top"

You can use Tailwind directly:

tsx
className="
  data-[state=open]:animate-in
  data-[state=closed]:animate-out
"

👉 This is the key to shadcn/ui animation

9. Controlled vs. Uncontrolled (must understand) ​

Uncontrolled (simple) ​

tsx
<Dialog defaultOpen />

Controlled (recommended in business) ​

tsx
<Dialog open={open} onOpenChange={setOpen} />

10. Take Dialog as an example (complete deconstruction) ​

tsx
<Dialog>
  <DialogTrigger asChild>
    <Button>Open</Button>
  </DialogTrigger>

  <DialogContent>
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>Desc</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

Radix is responsible for:

  • open status
  • ESC
  • focus trap
  • aria-describedby

shadcn/ui is responsible for:

  • className
  • animation
  • structural encapsulation

11. Common misunderstandings ​

  • ❌ Radix is used as a UI library
  • ❌Write business logic in Trigger
  • ❌ Ignore data-state
  • ❌ No need for asChild

12. The relationship between Radix UI + shadcn/ui ​

Radixshadcn/ui
Interaction primitivesStyle encapsulation
UnstyledTailwind
a11yDesign System
BehaviorVision

13. When do you “know Radix UI”? ​

You can:

  • Handwriting Dialog/Popover/Tabs
  • Correct use of asChild
  • Use data-state to write animations
  • Encapsulate instead of using Radix directly