Unstyled, accessible components for building high‑quality design systems and web apps in React.
With modal and non-modal modes, fine-grained focus control, accessible to screen readers.
With submenus, checkable items, collision handling, arrow key navigation, and typeahead support.
With fine-grained focus control, collision handling, origin-aware and collision-aware animations.
Supports keyboard and touch input, step interval, multiple thumbs for value ranges, and RTL direction.
Supports custom cross-browser styling while maintaining the browser's native scroll behavior.
Supports arrow key navigation, horizontal/vertical orientation, controlled or uncontrolled.
Supports one or multiple items open at the same time, keyboard navigation, collapse and expand animation.
With arrow key navigation, horizontal/vertical orientation support, controlled or uncontrolled.
A set of two-state buttons that can be toggled on or off. Supports single and multiple pressed buttons.
Allows the user to toggle between checked and not checked.
It takes a lot of time to develop and maintain robust UI components, and it's mostly undifferentiated work. Building on top of Radix components will save you time and money, so you can ship a better product faster.
It’s no secret that robust UI components are tricky to build. Nailing accessibility details and complex logic sucks time away from product feature development. With Radix, you can focus on your unique engineering challenges instead.
We agonise over API design, performance, and accessibility so you don't need to.
Radix Primitives follow the WAI-ARIA guidelines, implementing correct semantics and behaviors for our components.
Primitives provide full keyboard support for components where users expect to use a keyboard or other input devices.
Out of the box, Primitives provide sensible focus management defaults, which can be further customized in your code.
We test Primitives with common assistive technologies, looking out for practical issues that people may experience.
One of our main goals is to provide the best possible developer experience. Radix Primitives provides a fully-typed API. All components share a similar API, creating a consistent and predictable experience.
// Add styles with your preferred CSS technology
const TooltipContent = styled(Tooltip.Content, {
backgroundColor: 'black',
borderRadius: '3px',
padding: '5px'
});
const PopoverContent = styled(Popover.Content, {
backgroundColor: 'white',
boxShadow: '0 2px 10px -3px rgb(0 0 0 / 20%)',
borderRadius: '3px',
});
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
boxShadow: '0 3px 15px -4px rgb(0 0 0 / 30%)',
borderRadius: '5px',
});
// Add styles with your preferred CSS technology
const TooltipContent = styled(Tooltip.Content, {
backgroundColor: 'black',
borderRadius: '3px',
padding: '5px'
});
const PopoverContent = styled(Popover.Content, {
backgroundColor: 'white',
boxShadow: '0 2px 10px -3px rgb(0 0 0 / 20%)',
borderRadius: '3px',
});
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
boxShadow: '0 3px 15px -4px rgb(0 0 0 / 30%)',
borderRadius: '5px',
});
No need to override styles, no specificity wars.
// Compose a custom Tooltip component
export const StatusTooltip = ({ state, label }) => {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Text>
<Status variant={state} />
</Text>
</Tooltip.Trigger>
<Tooltip.Portal>
<TooltipContent>
<Tooltip.Arrow />
{label}
</TooltipContent>
</Tooltip.Portal>
</Tooltip.Root>
);
};
// Compose a custom Tooltip component
export const StatusTooltip = ({ state, label }) => {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Text>
<Status variant={state} />
</Text>
</Tooltip.Trigger>
<Tooltip.Portal>
<TooltipContent>
<Tooltip.Arrow />
{label}
</TooltipContent>
</Tooltip.Portal>
</Tooltip.Root>
);
};
Granular access to each component part.
// Compose a Popover with custom focus and positioning
export const DeploymentPopover = ({ children }) => {
const popoverCloseButton = React.useRef(null);
return (
<Popover.Root>
<Popover.Trigger>View deployment</Popover.Trigger>
<Popover.Portal>
<PopoverContent
align="start"
collisionPadding={10}
onOpenAutoFocus={(event) => {
// Focus the close button when popover opens
popoverCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Popover.Close ref={popoverCloseButton}>
Close
</Popover.Close>
</PopoverContent>
</Popover.Portal>
</Popover.Root>
);
};
// Compose a Popover with custom focus and positioning
export const DeploymentPopover = ({ children }) => {
const popoverCloseButton = React.useRef(null);
return (
<Popover.Root>
<Popover.Trigger>View deployment</Popover.Trigger>
<Popover.Portal>
<PopoverContent
align="start"
collisionPadding={10}
onOpenAutoFocus={(event) => {
// Focus the close button when popover opens
popoverCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Popover.Close ref={popoverCloseButton}>
Close
</Popover.Close>
</PopoverContent>
</Popover.Portal>
</Popover.Root>
);
};
Configure behavior, control focus, add event listeners.
// Compose a Dialog with custom focus management
export const InfoDialog = ({ children }) => {
const dialogCloseButton = React.useRef(null);
return (
<Dialog.Root>
<Dialog.Trigger>View details</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Portal>
<DialogContent
onOpenAutoFocus={(event) => {
// Focus the close button when dialog opens
dialogCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Dialog.Close ref={dialogCloseButton}>
Close
</Dialog.Close>
</DialogContent>
</Dialog.Portal>
</Dialog.Root>
);
};
// Compose a Dialog with custom focus management
export const InfoDialog = ({ children }) => {
const dialogCloseButton = React.useRef(null);
return (
<Dialog.Root>
<Dialog.Trigger>View details</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Portal>
<DialogContent
onOpenAutoFocus={(event) => {
// Focus the close button when dialog opens
dialogCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Dialog.Close ref={dialogCloseButton}>
Close
</Dialog.Close>
</DialogContent>
</Dialog.Portal>
</Dialog.Root>
);
};
Components with similar functionality share similar API.
// Add styles with your preferred CSS technology
const TooltipContent = styled(Tooltip.Content, {
backgroundColor: 'black',
borderRadius: '3px',
padding: '5px'
});
const PopoverContent = styled(Popover.Content, {
backgroundColor: 'white',
boxShadow: '0 2px 10px -3px rgb(0 0 0 / 20%)',
borderRadius: '3px',
});
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
boxShadow: '0 3px 15px -4px rgb(0 0 0 / 30%)',
borderRadius: '5px',
});
// Compose a custom Tooltip component
export const StatusTooltip = ({ state, label }) => {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Text>
<Status variant={state} />
</Text>
</Tooltip.Trigger>
<Tooltip.Portal>
<TooltipContent>
<Tooltip.Arrow />
{label}
</TooltipContent>
</Tooltip.Portal>
</Tooltip.Root>
);
};
// Compose a Popover with custom focus and positioning
export const DeploymentPopover = ({ children }) => {
const popoverCloseButton = React.useRef(null);
return (
<Popover.Root>
<Popover.Trigger>View deployment</Popover.Trigger>
<Popover.Portal>
<PopoverContent
align="start"
collisionPadding={10}
onOpenAutoFocus={(event) => {
// Focus the close button when popover opens
popoverCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Popover.Close ref={popoverCloseButton}>
Close
</Popover.Close>
</PopoverContent>
</Popover.Portal>
</Popover.Root>
);
};
// Compose a Dialog with custom focus management
export const InfoDialog = ({ children }) => {
const dialogCloseButton = React.useRef(null);
return (
<Dialog.Root>
<Dialog.Trigger>View details</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Portal>
<DialogContent
onOpenAutoFocus={(event) => {
// Focus the close button when dialog opens
dialogCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Dialog.Close ref={dialogCloseButton}>
Close
</Dialog.Close>
</DialogContent>
</Dialog.Portal>
</Dialog.Root>
);
};
// Add styles with your preferred CSS technology
const TooltipContent = styled(Tooltip.Content, {
backgroundColor: 'black',
borderRadius: '3px',
padding: '5px'
});
const PopoverContent = styled(Popover.Content, {
backgroundColor: 'white',
boxShadow: '0 2px 10px -3px rgb(0 0 0 / 20%)',
borderRadius: '3px',
});
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
boxShadow: '0 3px 15px -4px rgb(0 0 0 / 30%)',
borderRadius: '5px',
});
// Compose a custom Tooltip component
export const StatusTooltip = ({ state, label }) => {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Text>
<Status variant={state} />
</Text>
</Tooltip.Trigger>
<Tooltip.Portal>
<TooltipContent>
<Tooltip.Arrow />
{label}
</TooltipContent>
</Tooltip.Portal>
</Tooltip.Root>
);
};
// Compose a Popover with custom focus and positioning
export const DeploymentPopover = ({ children }) => {
const popoverCloseButton = React.useRef(null);
return (
<Popover.Root>
<Popover.Trigger>View deployment</Popover.Trigger>
<Popover.Portal>
<PopoverContent
align="start"
collisionPadding={10}
onOpenAutoFocus={(event) => {
// Focus the close button when popover opens
popoverCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Popover.Close ref={popoverCloseButton}>
Close
</Popover.Close>
</PopoverContent>
</Popover.Portal>
</Popover.Root>
);
};
// Compose a Dialog with custom focus management
export const InfoDialog = ({ children }) => {
const dialogCloseButton = React.useRef(null);
return (
<Dialog.Root>
<Dialog.Trigger>View details</Dialog.Trigger>
<Dialog.Overlay />
<Dialog.Portal>
<DialogContent
onOpenAutoFocus={(event) => {
// Focus the close button when dialog opens
dialogCloseButton.current?.focus();
event.preventDefault();
}}
>
{children}
<Dialog.Close ref={dialogCloseButton}>
Close
</Dialog.Close>
</DialogContent>
</Dialog.Portal>
</Dialog.Root>
);
};
Each component is its own independently versioned package, so new components can be added alongside your existing code. No need to disrupt feature work with a huge rewrite—you can start small and add more components one by one.
Radix documentation contains real-world examples, extensive API references, accessibility details, and full TypeScript support. All components share a similar API, creating a consistent developer experience. You will love working with Radix Primitives.