Radix UI: Styling, Positioning & the shadcn/ui Pattern
Styling via Data Attributes
/* Radix has no default visual CSS -- but exposes internal state
via data attributes any styling approach can target */
.dialog-content {
background: white;
border-radius: 8px;
padding: 24px;
}
/* CSS reacts directly to Radix's open/closed state -- no need for
JavaScript to conditionally toggle a class name */
.dialog-content[data-state="open"] {
animation: fadeIn 150ms ease-out;
}
.dialog-content[data-state="closed"] {
animation: fadeOut 100ms ease-in;
}Controlled vs. Uncontrolled Usage
// Uncontrolled -- Radix manages open/closed state internally,
// driven purely by Trigger/Close interactions
<Dialog.Root>...</Dialog.Root>
// Controlled -- application code owns the state, useful when opening
// needs to be triggered by logic other than clicking the Trigger
// (e.g. after a successful form submission elsewhere in the app)
const [isOpen, setIsOpen] = useState(false)
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
...
</Dialog.Root>Automatic Collision-Aware Positioning
import * as Popover from '@radix-ui/react-popover'
// Content automatically flips/shifts to stay within the viewport --
// a Popover near the bottom edge opens upward instead of being cut off
<Popover.Root>
<Popover.Trigger>Info</Popover.Trigger>
<Popover.Portal>
<Popover.Content side="bottom" collisionPadding={8}>
Helpful details here.
</Popover.Content>
</Popover.Portal>
</Popover.Root>shadcn/ui: Radix + Tailwind, Pre-Styled
shadcn/ui is a popular collection of components built on top of Radix Primitives, styled with Tailwind CSS, and distributed as copy-pasteable source (not an opaque npm dependency) -- combining Radix's accessible behavior with ready-made visual design that's still fully editable in your own codebase. A good starting point for teams that want Radix's correctness without styling every primitive from scratch.
When to Reach for Radix
A project with its own distinct design system, where a pre-styled library like Material UI would mean fighting its default styles.
Any interactive pattern with real accessibility complexity -- dialogs, dropdown menus, tooltips, select inputs -- rather than reimplementing focus/keyboard/ARIA logic from scratch.
Each primitive (@radix-ui/react-dialog, @radix-ui/react-tooltip, etc.) is installed as its own package, so a project pulls in only what it needs.
Radix UI Primitives targets React specifically; a separate, independently maintained project exists for Vue.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free