Radix UI Cheat Sheet
Overview
Radix UI is a collection of low-level, unstyled, and accessible React component primitives. Each primitive handles complex behaviors like keyboard navigation, focus management, screen reader support, and collision-aware positioning while leaving all visual styling to you. This makes Radix ideal for building custom design systems without fighting against pre-existing styles.
Radix primitives follow WAI-ARIA patterns and are fully composable. They are used as the foundation for popular styled libraries like shadcn/ui. Each component is published as a separate npm package, so you only install what you need. Radix also provides additional utilities including Colors (a color system), Icons, and Themes (a pre-styled component library built on the primitives).
Installation
# Install individual primitives as needed
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-popover
npm install @radix-ui/react-tabs
npm install @radix-ui/react-tooltip
npm install @radix-ui/react-accordion
npm install @radix-ui/react-select
npm install @radix-ui/react-slider
npm install @radix-ui/react-switch
npm install @radix-ui/react-checkbox
npm install @radix-ui/react-radio-group
npm install @radix-ui/react-toggle
npm install @radix-ui/react-toggle-group
npm install @radix-ui/react-avatar
npm install @radix-ui/react-hover-card
npm install @radix-ui/react-navigation-menu
npm install @radix-ui/react-context-menu
npm install @radix-ui/react-alert-dialog
npm install @radix-ui/react-toast
# Install Radix Themes (pre-styled components)
npm install @radix-ui/themes
# Install Radix Colors
npm install @radix-ui/colors
Core Components
Dialog (Modal)
import * as Dialog from "@radix-ui/react-dialog"
function MyDialog() {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button>Open Dialog</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Edit Profile</Dialog.Title>
<Dialog.Description>
Make changes to your profile here.
</Dialog.Description>
<input placeholder="Name" />
<Dialog.Close asChild>
<button>Save</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
Dropdown Menu
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
function MyDropdown() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button>Options</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="dropdown-content" sideOffset={5}>
<DropdownMenu.Item className="dropdown-item">
New File
</DropdownMenu.Item>
<DropdownMenu.Item className="dropdown-item">
Open File
</DropdownMenu.Item>
<DropdownMenu.Separator className="dropdown-separator" />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger className="dropdown-item">
Export As
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
<DropdownMenu.SubContent className="dropdown-content">
<DropdownMenu.Item className="dropdown-item">PNG</DropdownMenu.Item>
<DropdownMenu.Item className="dropdown-item">SVG</DropdownMenu.Item>
<DropdownMenu.Item className="dropdown-item">PDF</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Portal>
</DropdownMenu.Sub>
<DropdownMenu.Separator className="dropdown-separator" />
<DropdownMenu.CheckboxItem
className="dropdown-item"
checked={true}
>
<DropdownMenu.ItemIndicator>✓</DropdownMenu.ItemIndicator>
Show Grid
</DropdownMenu.CheckboxItem>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}
Tabs
import * as Tabs from "@radix-ui/react-tabs"
function MyTabs() {
return (
<Tabs.Root defaultValue="tab1" className="tabs-root">
<Tabs.List className="tabs-list" aria-label="Manage settings">
<Tabs.Trigger className="tabs-trigger" value="tab1">General</Tabs.Trigger>
<Tabs.Trigger className="tabs-trigger" value="tab2">Security</Tabs.Trigger>
<Tabs.Trigger className="tabs-trigger" value="tab3">Billing</Tabs.Trigger>
</Tabs.List>
<Tabs.Content className="tabs-content" value="tab1">
<p>General settings content</p>
</Tabs.Content>
<Tabs.Content className="tabs-content" value="tab2">
<p>Security settings content</p>
</Tabs.Content>
<Tabs.Content className="tabs-content" value="tab3">
<p>Billing settings content</p>
</Tabs.Content>
</Tabs.Root>
)
}
Available Primitives
| Component | Package | Description |
|---|---|---|
| Accordion | react-accordion | Collapsible content sections |
| Alert Dialog | react-alert-dialog | Modal requiring acknowledgment |
| Aspect Ratio | react-aspect-ratio | Maintain width/height ratio |
| Avatar | react-avatar | User avatar with fallback |
| Checkbox | react-checkbox | Checkbox input |
| Collapsible | react-collapsible | Expandable content |
| Context Menu | react-context-menu | Right-click menu |
| Dialog | react-dialog | Modal overlay |
| Dropdown Menu | react-dropdown-menu | Click-triggered menu |
| Hover Card | react-hover-card | Content on hover |
| Navigation Menu | react-navigation-menu | Site navigation |
| Popover | react-popover | Floating content panel |
| Progress | react-progress | Progress indicator |
| Radio Group | react-radio-group | Radio button group |
| Select | react-select | Custom select dropdown |
| Separator | react-separator | Visual divider |
| Slider | react-slider | Range input |
| Switch | react-switch | Toggle switch |
| Tabs | react-tabs | Tabbed interface |
| Toast | react-toast | Notification popups |
| Toggle | react-toggle | Toggle button |
| Toolbar | react-toolbar | Toolbar with groups |
| Tooltip | react-tooltip | Hover tooltip |
Configuration
Styling with CSS
/* Radix provides data attributes for styling states */
/* Open/closed state */
[data-state="open"] {
background-color: #f0f0f0;
}
[data-state="closed"] {
background-color: transparent;
}
/* Dialog overlay animation */
.dialog-overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.dialog-overlay[data-state="open"] {
animation: fadeIn 150ms ease-out;
}
.dialog-overlay[data-state="closed"] {
animation: fadeOut 150ms ease-in;
}
/* Dropdown content */
.dropdown-content {
min-width: 220px;
background: white;
border-radius: 6px;
padding: 5px;
box-shadow: 0 10px 38px -10px rgba(0, 0, 0, 0.35);
}
.dropdown-content[data-side="top"] { animation-name: slideDown; }
.dropdown-content[data-side="bottom"] { animation-name: slideUp; }
/* Highlighted item */
.dropdown-item[data-highlighted] {
background-color: #4c9aff;
color: white;
}
/* Disabled item */
.dropdown-item[data-disabled] {
opacity: 0.5;
pointer-events: none;
}
Styling with Tailwind CSS
<Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out" />
<Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out" />
<DropdownMenu.Item className="flex cursor-pointer items-center rounded-md px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-blue-500 data-[highlighted]:text-white" />
Advanced Usage
Controlled Components
import { useState } from "react"
import * as Dialog from "@radix-ui/react-dialog"
function ControlledDialog() {
const [open, setOpen] = useState(false)
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<button>Open</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content>
<p>Controlled dialog content</p>
<button onClick={() => setOpen(false)}>Close programmatically</button>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
Composition with asChild
// asChild merges props onto the child element instead of rendering a default element
<Dialog.Trigger asChild>
<MyCustomButton variant="primary">Open</MyCustomButton>
</Dialog.Trigger>
// Without asChild, Radix renders its own <button>
<Dialog.Trigger>Open</Dialog.Trigger>
Toast Notifications
import * as Toast from "@radix-ui/react-toast"
function Notifications() {
const [open, setOpen] = useState(false)
return (
<Toast.Provider swipeDirection="right">
<button onClick={() => setOpen(true)}>Show Toast</button>
<Toast.Root className="toast" open={open} onOpenChange={setOpen}>
<Toast.Title>Success</Toast.Title>
<Toast.Description>Your changes have been saved.</Toast.Description>
<Toast.Action altText="Undo" asChild>
<button>Undo</button>
</Toast.Action>
<Toast.Close>Dismiss</Toast.Close>
</Toast.Root>
<Toast.Viewport className="toast-viewport" />
</Toast.Provider>
)
}
Radix Themes (Pre-Styled)
import "@radix-ui/themes/styles.css"
import { Theme, Button, Flex, Text, Card } from "@radix-ui/themes"
function App() {
return (
<Theme appearance="dark" accentColor="blue" radius="medium">
<Card>
<Flex direction="column" gap="3">
<Text size="5" weight="bold">Welcome</Text>
<Text color="gray">Get started with Radix Themes.</Text>
<Button>Get Started</Button>
</Flex>
</Card>
</Theme>
)
}
Troubleshooting
| Issue | Solution |
|---|---|
| Component not rendering | Ensure you wrap content in Portal for overlays (Dialog, Dropdown, etc.) |
| Styles not applying on state change | Use data-state attribute selectors, not class toggling |
| Focus trap not working | Ensure Dialog/AlertDialog Content is inside a Portal |
| Click outside not closing | Check that Overlay is rendered and covers the viewport |
| Keyboard navigation broken | Do not override onKeyDown unless calling the original handler |
| asChild not merging props | The child must accept and forward ref and all props |
| Animation on close not playing | Use data-state="closed" in CSS; ensure component unmounts after animation |
| TypeScript errors | Install @types/react and use generic types from Radix typings |
| z-index conflicts | Adjust z-index on Portal content; Radix portals render at document body |