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
Navigation
- 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
| Radix | shadcn/ui |
|---|---|
| Interaction primitives | Style encapsulation |
| Unstyled | Tailwind |
| a11y | Design System |
| Behavior | Vision |
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