base-ui-react
ClaudeSkillz: For when you need skills, but lazier
npx skills add https://github.com/jackspace/claudeskillz --skill base-ui-reactSKILL.md
Base UI React
Status: Beta (v1.0.0-beta.4) - Stable v1.0 expected Q4 2025 Last Updated: 2025-11-07 Dependencies: React 19+, Vite (recommended), Tailwind v4 (recommended) Latest Versions: @base-ui-components/react@1.0.0-beta.4
⚠️ Important Beta Status Notice
Base UI is currently in beta. Before using in production:
- ✅ Stable: Core components (Dialog, Popover, Tooltip, Select, Accordion) are production-ready
- ⚠️ API May Change: Minor breaking changes possible before v1.0 (Q4 2025)
- ✅ Production Tested: Used in real projects with documented workarounds
- ⚠️ Known Issues: 10+ documented issues with solutions in this skill
- ✅ Migration Path: Clear migration guide from Radix UI included
Recommendation: Use for new projects comfortable with beta software. Wait for v1.0 for critical production apps.
Quick Start (5 Minutes)
1. Install Base UI
pnpm add @base-ui-components/react
Why this matters:
- Single package contains all 27+ accessible components
- No peer dependencies besides React
- Tree-shakeable - only import what you need
- Works with any styling solution (Tailwind, CSS Modules, Emotion, etc.)
2. Use Your First Component
// src/App.tsx
import { Dialog } from "@base-ui-components/react/dialog";
export function App() {
return (
<Dialog.Root>
{/* Render prop pattern - Base UI's key feature */}
<Dialog.Trigger
render={(props) => (
<button {...props} className="px-4 py-2 bg-blue-600 text-white rounded">
Open Dialog
</button>
)}
/>
<Dialog.Portal>
<Dialog.Backdrop
render={(props) => (
<div {...props} className="fixed inset-0 bg-black/50" />
)}
/>
<Dialog.Popup
render={(props) => (
<div
{...props}
className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-6"
>
<Dialog.Title render={(titleProps) => (
<h2 {...titleProps} className="text-2xl font-bold mb-4">
Dialog Title
</h2>
)} />
<Dialog.Description render={(descProps) => (
<p {...descProps} className="text-gray-600 mb-6">
This is a Base UI dialog. Fully accessible, fully styled by you.
</p>
)} />
<Dialog.Close render={(closeProps) => (
<button {...closeProps} className="px-4 py-2 border rounded">
Close
</button>
)} />
</div>
)}
/>
</Dialog.Portal>
</Dialog.Root>
);
}
CRITICAL:
- ✅ Always spread
{...props}from render functions - ✅ Use
<Dialog.Portal>to render outside DOM hierarchy - ✅
BackdropandPopupare separate components (unlike Radix's combinedOverlay + Content)
3. Components with Positioning (Select, Popover, Tooltip)
For components that need smart positioning, wrap in Positioner:
import { Popover } from "@base-ui-components/react/popover";
<Popover.Root>
<Popover.Trigger
render={(props) => <button {...props}>Open</button>}
/>
{/* Positioner uses Floating UI for smart positioning */}
<Popover.Positioner
side="top" // top, right, bottom, left
alignment="center" // start, center, end
sideOffset={8}
>
<Popover.Portal>
<Popover.Popup
render={(props) => (
<div {...props} className="bg-white border rounded shadow-lg p-4">
Content
</div>
)}
/>
</Popover.Portal>
</Popover.Positioner>
</Popover.Root>
The Render Prop Pattern (vs Radix's asChild)
Why Render Props?
Base UI uses render props instead of Radix's asChild pattern. This provides:
✅ Explicit prop spreading - Clear what props are being applied ✅ Better TypeScript support - Full type inference for props ✅ Easier debugging - Inspect props in dev tools ✅ Composition flexibility - Combine multiple render functions
Comparison
Radix UI (asChild):
import * as Dialog from "@radix-ui/react-dialog";
<Dialog.Trigger asChild>
<button>Open</button>
</Dialog.Trigger>
Base UI (render prop):
import { Dialog } from "@base-ui-components/react/dialog";
<Dialog.Trigger
render={(props) => (
<button {...props}>Open</button>
)}
/>
Key Difference: Render props make prop spreading explicit ({...props}), while asChild does it implicitly.
The Positioner Pattern (Floating UI Integration)
Components that float (Select, Popover, Tooltip) use the Positioner pattern:
Without Positioner (Wrong)
// ❌ This won't position correctly
<Popover.Root>
<Popover.Trigger />
<Popover.Popup /> {/* Missing positioning logic */}
</Popover.Root>
...