Radix Primitives
Unstyled, accessible React components for building high-quality design systems.
Overview
- Building custom styled components with full accessibility
- Understanding how shadcn/ui works under the hood
- Need polymorphic composition without wrapper divs
- Implementing complex UI patterns (modals, menus, tooltips)
Primitives Catalog
Overlay Components
| Primitive |
Use Case |
| Dialog |
Modal dialogs, forms, confirmations |
| AlertDialog |
Destructive action confirmations |
| Sheet |
Side panels, mobile drawers |
Popover Components
| Primitive |
Use Case |
| Popover |
Rich content on trigger |
| Tooltip |
Simple text hints |
| HoverCard |
Preview cards on hover |
| ContextMenu |
Right-click menus |
Menu Components
| Primitive |
Use Case |
| DropdownMenu |
Action menus |
| Menubar |
Application menubars |
| NavigationMenu |
Site navigation |
Form Components
| Primitive |
Use Case |
| Select |
Custom select dropdowns |
| RadioGroup |
Single selection groups |
| Checkbox |
Boolean toggles |
| Switch |
On/off toggles |
| Slider |
Range selection |
Disclosure Components
| Primitive |
Use Case |
| Accordion |
Expandable sections |
| Collapsible |
Single toggle content |
| Tabs |
Tabbed interfaces |
Core Pattern: asChild
The asChild prop enables polymorphic rendering without wrapper divs:
// Without asChild - creates nested elements
<Button>
<Link href="/about">About</Link>
</Button>
// With asChild - merges into single element
<Button asChild>
<Link href="/about">About</Link>
</Button>
How it works:
- Uses Radix's internal
Slot component
- Merges props from parent to child
- Forwards refs correctly
- Combines event handlers (both fire)
- Merges classNames
Built-in Accessibility
Every primitive includes:
- Keyboard navigation: Arrow keys, Escape, Enter, Tab
- Focus management: Trap, return, visible focus rings
- ARIA attributes: Roles, states, properties
- Screen reader: Proper announcements
Styling with Data Attributes
Radix exposes state via data attributes:
/* Style based on state */
[data-state="open"] { /* open styles */ }
[data-state="closed"] { /* closed styles */ }
[data-disabled] { /* disabled styles */ }
[data-highlighted] { /* keyboard focus */ }
// Tailwind arbitrary variants
<Dialog.Content className="data-[state=open]:animate-in data-[state=closed]:animate-out">
Quick Reference
import { Dialog, DropdownMenu, Tooltip } from 'radix-ui'
// Basic Dialog
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Title</Dialog.Title>
<Dialog.Description>Description</Dialog.Description>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Key Decisions
| Decision |
Recommendation |
| Styling approach |
Data attributes + Tailwind arbitrary variants |
| Composition |
Use asChild to avoid wrapper divs |
| Animation |
CSS-only with data-state selectors |
| Form components |
Combine with react-hook-form |
Related Skills
shadcn-patterns - Pre-styled Radix components
focus-management - Accessibility patterns
design-system-starter - Design system foundation
References