Radix UI Primitives
Quick Guide: Radix UI provides unstyled, accessible primitives for building design systems. Use compound component patterns (Root, Trigger, Content),asChildfor polymorphism, anddata-stateattributes for animations. Focus on behavior and accessibility - defer styling decisions to your styling solution. Current: v1.4.x (May 2025) - Full React 19 and RSC compatibility with new preview primitives.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST use compound component anatomy - Root, Trigger, Portal, Content, Close - for overlay components)
(You MUST use forwardRef and spread all props when using asChild with custom components - unless using React 19+ where ref is a regular prop)
(You MUST use Portal for overlays to escape CSS stacking contexts and parent overflow constraints)
(You MUST provide accessible labels via Title/Description components or ARIA attributes - Dialog logs console errors for missing Title)
</critical_requirements>
Auto-detection: Radix UI, radix-ui, @radix-ui, Dialog, Dropdown, DropdownMenu, Select, Popover, Tooltip, Accordion, Tabs, AlertDialog, asChild, Slot, Portal, data-state, OneTimePasswordField, PasswordToggleField, unstable_Form, Form.Field, Form.Message
When to use:
- Building accessible overlay components (dialogs, popovers, dropdowns, tooltips)
- Creating compound component APIs with multiple coordinated parts
- Implementing keyboard navigation and focus management
- Needing polymorphic components via
asChildpattern
When NOT to use:
- Pre-styled components desired (use a component library built on Radix)
- Simple components without complex interactions (use plain HTML)
- Non-React projects (Radix primitives are React-specific)
Package Installation:
# Recommended: Unified tree-shakeable package (prevents version conflicts)
npm i radix-ui
# Alternative: Individual packages
npm i @radix-ui/react-dialog @radix-ui/react-dropdown-menu
Detailed Resources:
- For core code examples (Dialog, asChild, Slot), see [examples/core.md](examples/core.md)
- For overlay patterns (AlertDialog, controlled Dialog), see [examples/overlays.md](examples/overlays.md)
- For menu patterns (DropdownMenu, submenus), see [examples/menus.md](examples/menus.md)
- For animation patterns (data-state, CSS keyframes), see [examples/animation.md](examples/animation.md)
- For form patterns (Select), see [examples/forms.md](examples/forms.md)
- For navigation patterns (Accordion, Tabs), see [examples/navigation.md](examples/navigation.md)
- For preview components (OneTimePasswordField, PasswordToggleField), see [examples/preview.md](examples/preview.md)
- For decision frameworks and anti-patterns, see [reference.md](reference.md)
<philosophy>
Philosophy
Radix UI Primitives provide behavioral and accessibility foundations without imposing visual design. Each primitive handles:
- Accessibility: ARIA attributes, roles, keyboard navigation, focus management
- Behavior: Open/close state, dismissal patterns, collision detection
- Composition: Compound components that work together as coordinated systems
Radix is styling-agnostic: Apply styles via className prop using your styling solution. The primitives expose data-state attributes for state-based styling.
Compound Component Model: Each primitive consists of multiple parts (Root, Trigger, Content, etc.) that share context. This enables flexible composition while maintaining coordinated behavior.
React 19 & RSC Support (v1.4.3): Full compatibility with React 19 and React Server Components. Enhanced keyboard handling avoids browser hotkey interference.
</philosophy>
<patterns>
Core Patterns
Pattern 1: Compound Component Anatomy
Radix primitives use a compound component pattern where multiple parts work together through shared context.
Standard Structure for Overlay Components
import { Dialog } from "radix-ui";
// Root provides context and state management
// Trigger opens the dialog
// Portal renders content outside React tree
// Overlay covers the page
// Content contains the dialog body
// Close dismisses the dialog
// Title and Description provide accessibility
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className={className} />
<Dialog.Content className={className}>
<Dialog.Title>Dialog Title</Dialog.Title>
<Dialog.Description>Accessible description</Dialog.Description>
{/* Dialog content */}
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Why this structure: Root manages state and context, Portal escapes CSS stacking contexts, Overlay provides visual backdrop, Title/Description ensure screen reader accessibility
Pattern 2: Controlled vs Uncontrolled State
Radix primitives support both controlled and uncontrolled state patterns.
Uncontrolled (Radix Manages State)
// Let Radix manage internal state - simpler for basic use cases
<Dialog.Root defaultOpen={false}>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content>
{/* Content */}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
When to use: Simple dialogs without external state requirements
Controlled (You Manage State)
import { useState } from "react";
import { Dialog } from "radix-ui";
function ControlledDialog() {
const [open, setOpen] = useState(false);
const handleSave = async () => {
await saveData();
setOpen(false); // Programmatically close after async operation
};
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content>
<Dialog.Title>Edit Profile</Dialog.Title>
<button onClick={handleSave}>Save</button>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
When to use: Programmatic control needed (close after async, open from external trigger, sync with URL state)
Pattern 3: asChild for Polymorphism
The asChild prop enables Radix to merge behavior onto your custom components or different element types.
Changing Element Type
import { Tooltip } from "radix-ui";
// Tooltip trigger defaults to button, but you may want a link
<Tooltip.Root>
<Tooltip.Trigger asChild>
<a href="/docs">Documentation</a>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>View the docs</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
Why good: Radix passes all required props and event handlers to the anchor, maintaining accessibility
With Custom Components
import { forwardRef } from "react";
import { Dialog } from "radix-ui";
// Custom component MUST use forwardRef and spread props
const CustomButton = forwardRef<HTMLButtonElement, React.ComponentProps<"button">>(
({ className, ...props }, ref) => {
return <button ref={ref} className={className} {...props} />;
}
);
CustomButton.displayName = "CustomButton";
// Use with asChild
<Dialog.Trigger asChild>
<CustomButton className="custom-class">Open Dialog</CustomButton>
</Dialog.Trigger>
Why this works: forwardRef allows Radix to attach refs for positioning/focus, spreading props passes event handlers and ARIA attributes
Pattern 4: Building Custom asChild Components with Slot
Use the Slot utility to build your own components with asChild support.
import { forwardRef } from "react";
import { Slot } from "radix-ui";
export type ButtonProps = React.ComponentProps<"button"> & {
asChild?: boolean;
};
export const Button = forwardRef<HTMLButtonElement, Button