Radix UI: Compound Components & Accessibility
Radix UI is an unstyled, accessible component library for React. It provides correct behavior, state management, and full WAI-ARIA-compliant accessibility for hard-to-build UI patterns -- dialogs, dropdowns, tooltips, selects -- while leaving all visual styling entirely to the consumer.
Compound Component Structure
import * as Dialog from '@radix-ui/react-dialog'
function ConfirmDialog() {
return (
<Dialog.Root>
<Dialog.Trigger className="btn">Delete item</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Are you sure?</Dialog.Title>
<Dialog.Description>This cannot be undone.</Dialog.Description>
<Dialog.Close className="btn-secondary">Cancel</Dialog.Close>
<button onClick={handleDelete}>Confirm</button>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}Root holds shared state (open/closed) via React context, so nested pieces like Trigger and Close coordinate without prop-drilling. Each piece -- Trigger, Content, Close -- owns one responsibility, giving fine-grained control over markup structure that a single monolithic component with dozens of props would make awkward.
asChild: Merging Behavior onto Your Own Elements
// Without asChild: Trigger renders its own default <button>,
// wrapping MyCustomButton inside it -- invalid nested-button markup
<Dialog.Trigger><MyCustomButton>Open</MyCustomButton></Dialog.Trigger>
// With asChild: Radix merges its click handlers/ARIA attributes
// directly onto MyCustomButton, with no extra wrapping element
<Dialog.Trigger asChild>
<MyCustomButton>Open</MyCustomButton>
</Dialog.Trigger>Accessibility Handled Automatically
Focus trapping inside an open Dialog (Tab/Shift+Tab cycles only through its contents) and focus restoration to the trigger on close.
Full keyboard navigation for menus/selects -- arrow keys between items, Enter/Space to select, Escape to close, and typeahead.
Correct ARIA roles/attributes and screen reader announcements for every interactive pattern.
Portal (e.g. Dialog.Portal) renders overlay content near document.body, avoiding clipping by an ancestor's overflow:hidden or z-index stacking context.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free