| name | radix-ui |
| description | Builds accessible components with Radix UI primitives including dialogs, dropdowns, tooltips, and form controls. Use when building accessible interfaces, creating headless components, or implementing complex UI patterns. |
Radix UI
Unstyled, accessible component primitives for building high-quality design systems.
Quick Start
Install individual components:
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-tooltip
npm install @radix-ui/react-tabs
Dialog
import * as Dialog from '@radix-ui/react-dialog';
function Modal() {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button>Open Modal</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg">
<Dialog.Title className="text-lg font-bold">
Modal Title
</Dialog.Title>
<Dialog.Description className="text-gray-500 mt-2">
Modal description goes here.
</Dialog.Description>
<div className="mt-4">
<p>Modal content</p>
</div>
<Dialog.Close asChild>
<button className="absolute top-4 right-4">
Close
</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Controlled 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.Overlay />
<Dialog.Content>
<Dialog.Title>Confirm</Dialog.Title>
<div className="flex gap-2 mt-4">
<button onClick={() => setOpen(false)}>Cancel</button>
<button
onClick={() => {
handleConfirm();
setOpen(false);
}}
>
Confirm
</button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Dropdown Menu
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function UserMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="p-2 rounded-full hover:bg-gray-100">
<UserIcon />
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white rounded-lg shadow-lg p-2 min-w-[200px]"
sideOffset={5}
>
<DropdownMenu.Label className="px-2 py-1 text-sm text-gray-500">
My Account
</DropdownMenu.Label>
<DropdownMenu.Item
className="px-2 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none"
onSelect={() => navigate('/profile')}
>
Profile
</DropdownMenu.Item>
<DropdownMenu.Item className="px-2 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none">
Settings
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
<DropdownMenu.Item
className="px-2 py-2 rounded cursor-pointer hover:bg-red-100 text-red-600 outline-none"
onSelect={handleLogout}
>
Logout
</DropdownMenu.Item>
<DropdownMenu.Arrow className="fill-white" />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
}
With Submenu
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button>Menu</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Item>New Tab</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger className="flex items-center justify-between">
More Tools
<ChevronRightIcon />
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
<DropdownMenu.SubContent>
<DropdownMenu.Item>Developer Tools</DropdownMenu.Item>
<DropdownMenu.Item>Task Manager</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Portal>
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
Select
import * as Select from '@radix-ui/react-select';
import { ChevronDownIcon, CheckIcon } from '@radix-ui/react-icons';
function SelectDemo() {
return (
<Select.Root>
<Select.Trigger className="flex items-center justify-between w-[200px] px-3 py-2 border rounded">
<Select.Value placeholder="Select a fruit" />
<Select.Icon>
<ChevronDownIcon />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="bg-white rounded-lg shadow-lg overflow-hidden">
<Select.ScrollUpButton className="flex items-center justify-center h-6">
<ChevronUpIcon />
</Select.ScrollUpButton>
<Select.Viewport className="p-1">
<Select.Group>
<Select.Label className="px-6 py-1 text-sm text-gray-500">
Fruits
</Select.Label>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="orange">Orange</SelectItem>
</Select.Group>
<Select.Separator className="h-px bg-gray-200 my-1" />
<Select.Group>
<Select.Label className="px-6 py-1 text-sm text-gray-500">
Vegetables
</Select.Label>
<SelectItem value="carrot">Carrot</SelectItem>
<SelectItem value="potato">Potato</SelectItem>
</Select.Group>
</Select.Viewport>
<Select.ScrollDownButton className="flex items-center justify-center h-6">
<ChevronDownIcon />
</Select.ScrollDownButton>
</Select.Content>
</Select.Portal>
</Select.Root>
);
}
function SelectItem({ children, value }: { children: string; value: string }) {
return (
<Select.Item
value={value}
className="flex items-center px-6 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none data-[highlighted]:bg-gray-100"
>
<Select.ItemIndicator className="absolute left-1">
<CheckIcon />
</Select.ItemIndicator>
<Select.ItemText>{children}</Select.ItemText>
</Select.Item>
);
}
Tabs
import * as Tabs from '@radix-ui/react-tabs';
function TabsDemo() {
return (
<Tabs.Root defaultValue="account" className="w-full">
<Tabs.List className="flex border-b">
<Tabs.Trigger
value="account"
className="px-4 py-2 data-[state=active]:border-b-2 data-[state=active]:border-blue-500"
>
Account
</Tabs.Trigger>
<Tabs.Trigger
value="password"
className="px-4 py-2 data-[state=active]:border-b-2 data-[state=active]:border-blue-500"
>
Password
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="account" className="p-4">
<h3 className="font-bold">Account Settings</h3>
<p>Manage your account settings here.</p>
</Tabs.Content>
<Tabs.Content value="password" className="p-4">
<h3 className="font-bold">Password</h3>
<p>Change your password here.</p>
</Tabs.Content>
</Tabs.Root>
);
}
Tooltip
import * as Tooltip from '@radix-ui/react-tooltip';
function TooltipDemo() {
return (
<Tooltip.Provider delayDuration={300}>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button>Hover me</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="bg-gray-900 text-white px-3 py-2 rounded text-sm"
sideOffset={5}
>
Tooltip content
<Tooltip.Arrow className="fill-gray-900" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}
Popover
import * as Popover from '@radix-ui/react-popover';
function PopoverDemo() {
return (
<Popover.Root>
<Popover.Trigger asChild>
<button>Open Popover</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="bg-white rounded-lg shadow-lg p-4 w-[300px]"
sideOffset={5}
>
<div className="space-y-2">
<h4 className="font-medium">Dimensions</h4>
<div>
<label className="text-sm">Width</label>
<input className="w-full border rounded px-2 py-1" />
</div>
<div>
<label className="text-sm">Height</label>
<input className="w-full border rounded px-2 py-1" />
</div>
</div>
<Popover.Close asChild>
<button className="absolute top-2 right-2">
<Cross2Icon />
</button>
</Popover.Close>
<Popover.Arrow className="fill-white" />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
Accordion
import * as Accordion from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
function AccordionDemo() {
return (
<Accordion.Root type="single" collapsible className="w-full">
<Accordion.Item value="item-1" className="border-b">
<Accordion.Header>
<Accordion.Trigger className="flex items-center justify-between w-full py-4 group">
<span>Is it accessible?</span>
<ChevronDownIcon className="transition-transform group-data-[state=open]:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content className="pb-4 text-gray-600">
Yes. It adheres to the WAI-ARIA design pattern.
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="item-2" className="border-b">
<Accordion.Header>
<Accordion.Trigger className="flex items-center justify-between w-full py-4 group">
<span>Is it unstyled?</span>
<ChevronDownIcon className="transition-transform group-data-[state=open]:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content className="pb-4 text-gray-600">
Yes. It's completely unstyled by default.
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
}
Checkbox
import * as Checkbox from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
function CheckboxDemo() {
const [checked, setChecked] = useState<boolean | 'indeterminate'>(false);
return (
<div className="flex items-center gap-2">
<Checkbox.Root
checked={checked}
onCheckedChange={setChecked}
className="w-5 h-5 border rounded flex items-center justify-center data-[state=checked]:bg-blue-500 data-[state=checked]:border-blue-500"
>
<Checkbox.Indicator>
<CheckIcon className="text-white" />
</Checkbox.Indicator>
</Checkbox.Root>
<label>Accept terms</label>
</div>
);
}
Switch
import * as Switch from '@radix-ui/react-switch';
function SwitchDemo() {
return (
<div className="flex items-center gap-2">
<label htmlFor="airplane-mode">Airplane Mode</label>
<Switch.Root
id="airplane-mode"
className="w-11 h-6 bg-gray-200 rounded-full data-[state=checked]:bg-blue-500 relative"
>
<Switch.Thumb className="block w-5 h-5 bg-white rounded-full shadow transition-transform translate-x-0.5 data-[state=checked]:translate-x-[22px]" />
</Switch.Root>
</div>
);
}
Slider
import * as Slider from '@radix-ui/react-slider';
function SliderDemo() {
return (
<Slider.Root
defaultValue={[50]}
max={100}
step={1}
className="relative flex items-center w-full h-5"
>
<Slider.Track className="bg-gray-200 relative grow rounded-full h-1">
<Slider.Range className="absolute bg-blue-500 rounded-full h-full" />
</Slider.Track>
<Slider.Thumb className="block w-5 h-5 bg-white border-2 border-blue-500 rounded-full hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" />
</Slider.Root>
);
}
Alert Dialog
import * as AlertDialog from '@radix-ui/react-alert-dialog';
function DeleteConfirmation() {
return (
<AlertDialog.Root>
<AlertDialog.Trigger asChild>
<button className="text-red-600">Delete</button>
</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay className="fixed inset-0 bg-black/50" />
<AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg max-w-md">
<AlertDialog.Title className="text-lg font-bold">
Are you sure?
</AlertDialog.Title>
<AlertDialog.Description className="text-gray-500 mt-2">
This action cannot be undone. This will permanently delete the item.
</AlertDialog.Description>
<div className="flex justify-end gap-3 mt-4">
<AlertDialog.Cancel asChild>
<button className="px-4 py-2 border rounded">Cancel</button>
</AlertDialog.Cancel>
<AlertDialog.Action asChild>
<button className="px-4 py-2 bg-red-600 text-white rounded">
Delete
</button>
</AlertDialog.Action>
</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
);
}
Best Practices
- Use asChild - Compose with your own elements
- Style with data attributes -
data-[state=open] - Add animations - Use CSS transitions
- Keep accessible - Don't remove ARIA attributes
- Wrap in Portals - For proper stacking context
Common Mistakes
| Mistake | Fix |
|---|---|
| Missing Portal | Wrap content in Portal |
| Removing accessibility | Keep provided ARIA |
| Direct child styling | Use asChild pattern |
| Missing Provider | Add Tooltip.Provider |
| Wrong event handler | Use onSelect, not onClick |
Reference Files
- references/components.md - All components
- references/styling.md - Styling patterns
- references/accessibility.md - A11y features