원클릭으로
radix-ui
Radix UI primitive patterns. Use when building accessible, unstyled UI components like dialogs, dropdowns, tooltips, tabs, and selects. Covers Tailwind styling, keyboard navigation, animations, and portal management.
메뉴
Radix UI primitive patterns. Use when building accessible, unstyled UI components like dialogs, dropdowns, tooltips, tabs, and selects. Covers Tailwind styling, keyboard navigation, animations, and portal management.
SOC 직업 분류 기준
Biome 2.x linting and formatting patterns. Use when configuring code quality tools, setting up linting rules, formatting code, or integrating with CI/CD. Covers migration from ESLint/Prettier.
Hono 4.x web framework patterns. Use when building APIs, middleware, routing, or server-side applications. Covers multi-runtime support (Node, Bun, Cloudflare Workers), validation, CORS, and error handling.
React development patterns. Use when building React components, managing state, creating custom hooks, or optimizing React applications. Covers React 19 features, TypeScript integration, and composition patterns.
Tailwind CSS 4.x utility-first styling patterns. Use when building UI components, creating responsive layouts, implementing design systems, or customizing themes. Covers CSS-first configuration, @theme directive, and component patterns.
Vite 7.x build tool patterns. Use when configuring build setup, development server, environment variables, asset handling, or optimizing production builds for React applications.
Plan infrastructure capacity for expected load. Use when sizing systems, planning for growth, or analyzing resource requirements. Covers load estimation and resource sizing.
| name | radix-ui |
| description | Radix UI primitive patterns. Use when building accessible, unstyled UI components like dialogs, dropdowns, tooltips, tabs, and selects. Covers Tailwind styling, keyboard navigation, animations, and portal management. |
Platform: Web only. For mobile modals/sheets, see the expo-sdk and react-native-patterns skills.
Use Context7 MCP (
resolve-library-idthenquery-docs) for full API reference, all component props, and additional examples.
Unstyled, accessible UI primitives for React with built-in keyboard navigation, focus management, and ARIA attributes. Designed to be composed with Tailwind CSS and Framer Motion.
Version: Latest (individual packages) or radix-ui unified package
Install (individual packages):
pnpm add @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-select @radix-ui/react-tooltip @radix-ui/react-tabs
Install (unified package):
pnpm add radix-ui
The unified radix-ui package bundles all primitives - use this for simpler dependency management.
Adding a Dialog:
pnpm add @radix-ui/react-dialogAdding a Select:
pnpm add @radix-ui/react-selectAdding Tooltips:
pnpm add @radix-ui/react-tooltipimport * as Dialog from '@radix-ui/react-dialog';
// OR: import { Dialog } from 'radix-ui';
function ModalExample() {
const [open, setOpen] = useState(false);
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<button className="px-4 py-2 bg-blue-600 text-white rounded">Open Dialog</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
<Dialog.Content className="fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] max-h-[85vh] w-[90vw] max-w-[500px] rounded-lg bg-white p-6 shadow-lg">
{/* Title and Description are required for accessibility */}
<Dialog.Title className="text-lg font-semibold mb-2">Dialog Title</Dialog.Title>
<Dialog.Description className="text-sm text-gray-600 mb-4">
Screen readers announce this description.
</Dialog.Description>
<div className="flex justify-end gap-2 mt-6">
<Dialog.Close asChild>
<button className="px-4 py-2 border rounded">Cancel</button>
</Dialog.Close>
<button className="px-4 py-2 bg-blue-600 text-white rounded">Save</button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
Framer Motion animations: Use forceMount on Portal and wrap with AnimatePresence. Pass asChild to Overlay/Content and use motion.div as the child.
import * as Select from '@radix-ui/react-select';
import { ChevronDownIcon, CheckIcon } from '@radix-ui/react-icons';
function SelectExample() {
return (
<Select.Root defaultValue="apple">
<Select.Trigger className="inline-flex items-center justify-between rounded px-4 py-2 text-sm bg-white border gap-2 data-[placeholder]:text-gray-400 min-w-[200px]">
<Select.Value placeholder="Select a fruit..." />
<Select.Icon><ChevronDownIcon /></Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg border">
<Select.Viewport className="p-1">
<Select.Item value="apple" className="relative flex items-center px-8 py-2 rounded text-sm hover:bg-blue-50 outline-none cursor-pointer data-[disabled]:opacity-50">
<Select.ItemIndicator className="absolute left-2"><CheckIcon /></Select.ItemIndicator>
<Select.ItemText>Apple</Select.ItemText>
</Select.Item>
<Select.Item value="banana" className="relative flex items-center px-8 py-2 rounded text-sm hover:bg-blue-50 outline-none cursor-pointer">
<Select.ItemIndicator className="absolute left-2"><CheckIcon /></Select.ItemIndicator>
<Select.ItemText>Banana</Select.ItemText>
</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
);
}
Use Select.Group + Select.Label for grouped options, Select.Separator for dividers.
// Open/closed state
className="data-[state=open]:bg-blue-50 data-[state=closed]:bg-gray-50"
// Disabled
className="data-[disabled]:opacity-50 data-[disabled]:pointer-events-none"
// Checked (checkboxes, radio items)
className="data-[state=checked]:bg-blue-600"
// Side-based positioning
className="data-[side=top]:animate-slide-down data-[side=bottom]:animate-slide-up"
// Focus ring (required for keyboard UX)
className="outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
// Centered modal
className="fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%]"
// Backdrop overlay
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
Keyboard navigation (handled automatically):
ARIA (added automatically):
role="dialog", aria-labelledby (Title), aria-describedby (Description)role="combobox", aria-expanded, aria-controlsAlways provide Dialog.Title and Dialog.Description — screen readers depend on them.
Custom focus management:
<Dialog.Content
onOpenAutoFocus={(e) => { e.preventDefault(); customRef.current?.focus(); }}
onCloseAutoFocus={(e) => { e.preventDefault(); triggerRef.current?.focus(); }}
>
// Uncontrolled — component manages its own state
<Dialog.Root defaultOpen={false}>
// Controlled — parent manages state (preferred for complex UIs)
const [open, setOpen] = useState(false);
<Dialog.Root open={open} onOpenChange={setOpen}>
Always portal overlays and dropdowns to avoid z-index conflicts, overflow: hidden clipping, and CSS transform issues:
<Dialog.Portal> {/* renders at document.body */}
<Dialog.Overlay />
<Dialog.Content />
</Dialog.Portal>
{/* Custom container */}
<Dialog.Portal container={customContainerRef.current}>
asChild prop to compose with custom elements without wrapper divsDialog.Portal (causes z-index issues)Dialog.Title or Dialog.Description (fails a11y)asChild with custom triggers (creates wrapper divs)TooltipProvider instances (unnecessary overhead)onOpenChange/onValueChangefocus:ring styles (poor keyboard UX)Accessibility testing:
# Test with keyboard only (no mouse)
# Tab through all interactive elements
# Esc should close Dialogs, Dropdowns, Selects
# Arrow keys should navigate menus and selects
Screen reader testing:
# macOS: VoiceOver (Cmd+F5)
# Verify Dialog.Title and Dialog.Description are announced
# Check for proper role attributes
Visual regression: Test all states: Closed vs Open, Hover vs Focus, Selected vs Unselected, Disabled, different viewport sizes.
Framer Motion: Use forceMount + AnimatePresence. Test that focus management still works with animations.