CSS Anchor Positioning and the native Popover API reached full Baseline support across major browsers in 2026. That means shadcn/ui's Popover and Tooltip, both built on Radix UI primitives, can now be rebuilt with zero JavaScript for positioning.
Radix (via Floating UI) has always handled the hard part: measuring the trigger, calculating placement, listening for scroll/resize, flipping on overflow. That required JS because the browser couldn't do it natively. Now it can.
What you gain and lose
| Dimension | Radix + Floating UI | Native CSS Anchor Positioning |
|---|---|---|
| Bundle size | Adds a dependency per component | Zero JS for positioning |
| Runtime cost | Recalculates on scroll/resize | Browser handles it natively |
| Viewport flip | Floating UI's flip() middleware |
position-try-fallbacks |
| Focus trapping | Built in | You implement it, or keep Radix |
| Keyboard nav | Built into Radix | Manual for complex cases |
| Browser support | Works everywhere JS runs | Needs Baseline 2026 + Safari fallback |
For a simple Tooltip or a Popover holding text and a button, native CSS wins outright. For DropdownMenu or Select, keep Radix, the focus trapping and keyboard navigation aren't worth rebuilding by hand yet.
The shape of the migration
The native Popover component uses anchor-name to mark the trigger, anchor() to position the content relative to it, and the browser's built-in popover attribute for top-layer rendering, light-dismiss, and Escape-to-close:
const PopoverContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, style, ...props }, ref) => {
const ctx = React.useContext(PopoverContext)
return (
<div
ref={ref}
id={ctx.contentId}
popover="auto"
style={{
positionAnchor: `--${ctx.triggerId}`,
top: `anchor(--${ctx.triggerId} bottom)`,
positionTryFallbacks: "flip-block, flip-inline",
...style,
}}
{...props}
/>
)
}
)
No Portal, no scroll/resize listeners, no Floating UI middleware to configure. popover="auto" gives light-dismiss and Escape-to-close for free. Tooltip follows the same pattern with popover="hint" instead.
One catch: Safari 18.2-18.3 support anchor() but not position-try-fallbacks. Write your base position as a plain value outside the fallback block and it degrades gracefully, no @supports check needed.
Adoption order
- Tooltip first - lowest risk, no focus management to preserve
- Simple Popovers next - text, single button, one-field forms
- Leave DropdownMenu and Select on Radix - the keyboard nav and focus trapping rebuild isn't worth it yet
- Measure bundle size before and after each migration to justify the effort
The full post has the complete before/after code for both components, the Safari fallback pattern in detail, and what you still need Radix for:
Top comments (0)