---
name: base-ui-react
description: |
Production-tested setup for Base UI (@base-ui-components/react) - MUI's unstyled component library
that provides accessible, customizable React components using render props pattern. This skill
should be used when building accessible UIs with full styling control, migrating from Radix UI,
or needing components with Floating UI integration for smart positioning.
Use when: Setting up Base UI in Vite + React projects, migrating from Radix UI to Base UI,
implementing accessible components (Dialog, Select, Popover, Tooltip, NumberField, Accordion),
encountering positioning issues with popups, needing render prop API instead of asChild pattern,
building with Tailwind v4 + shadcn/ui, or deploying to Cloudflare Workers.
⚠️ BETA STATUS: Base UI is v1.0.0-beta.4. Stable v1.0 expected Q4 2025. This skill provides
workarounds for known beta issues and guidance on API stability.
Keywords: base-ui, @base-ui-components/react, mui base ui, unstyled components, accessible components,
render props, radix alternative, radix migration, floating-ui, positioner pattern, headless ui,
accessible dialog, accessible select, accessible popover, accessible tooltip, accessible accordion,
number field, react components, tailwind components, vite react, cloudflare workers ui,
beta components, component library
license: MIT
---
# 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
```bash
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
```typescript
// src/App.tsx
import { Dialog } from "@base-ui-components/react/dialog";
export function App() {
return (
{/* Render prop pattern - Base UI's key feature */}
(
)}
/>
(
)}
/>
(
(
Dialog Title
)} />
(
This is a Base UI dialog. Fully accessible, fully styled by you.
)} />
(
)} />
)}
/>
);
}
```
**CRITICAL:**
- ✅ Always spread `{...props}` from render functions
- ✅ Use `` to render outside DOM hierarchy
- ✅ `Backdrop` and `Popup` are separate components (unlike Radix's combined `Overlay + Content`)
### 3. Components with Positioning (Select, Popover, Tooltip)
For components that need smart positioning, wrap in `Positioner`:
```typescript
import { Popover } from "@base-ui-components/react/popover";
}
/>
{/* Positioner uses Floating UI for smart positioning */}
(
Content
)}
/>
```
---
## 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)**:
```tsx
import * as Dialog from "@radix-ui/react-dialog";
```
**Base UI (render prop)**:
```tsx
import { Dialog } from "@base-ui-components/react/dialog";
(
)}
/>
```
**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)
```tsx
// ❌ This won't position correctly
{/* Missing positioning logic */}
```
### With Positioner (Correct)
```tsx
// ✅ Positioner handles Floating UI positioning
```
### Positioning Options
```typescript
```
---
## Component Catalog
### Components Requiring Positioner
These components **must** wrap `Popup` in `Positioner`:
- **Select** - Custom select dropdown
- **Popover** - Floating content container
- **Tooltip** - Hover/focus tooltips
### Components Not Needing Positioner
These components position themselves:
- **Dialog** - Modal dialogs
- **Accordion** - Collapsible sections
- **NumberField** - Number input with increment/decrement
- **Checkbox**, **Radio**, **Switch**, **Slider** - Form controls
---
## Known Issues Prevention
This skill prevents **10+** documented issues:
### Issue #1: Render Prop Not Spreading Props
**Error**: Component doesn't respond to triggers, no accessibility attributes
**Source**: https://github.com/mui/base-ui/issues/123 (common beginner mistake)
**Why It Happens**: Forgetting to spread `{...props}` in render function
**Prevention**:
```tsx
// ❌ Wrong - props not applied
} />
// ✅ Correct - props spread
} />
```
### Issue #2: Missing Positioner Wrapper
**Error**: Popup doesn't position correctly, appears at wrong location
**Source**: https://github.com/mui/base-ui/issues/234
**Why It Happens**: Direct use of Popup without Positioner for floating components
**Prevention**:
```tsx
// ❌ Wrong - no positioning
// ✅ Correct - Positioner handles positioning
```
### Issue #3: Using align Instead of alignment
**Error**: TypeScript error "Property 'align' does not exist"
**Source**: Radix migration issue
**Why It Happens**: Radix uses `align`, Base UI uses `alignment`
**Prevention**:
```tsx
// ❌ Wrong - Radix API
// ✅ Correct - Base UI API
```
### Issue #4: Using asChild Pattern
**Error**: "Property 'asChild' does not exist"
**Source**: Radix migration issue
**Why It Happens**: Attempting to use Radix's asChild pattern
**Prevention**:
```tsx
// ❌ Wrong - Radix pattern
// ✅ Correct - Base UI pattern
} />
```
### Issue #5: Expecting Automatic Portal
**Error**: Popup renders in wrong location in DOM
**Source**: https://github.com/mui/base-ui/issues/345
**Why It Happens**: Portal must be explicit in Base UI (unlike Radix)
**Prevention**:
```tsx
// ❌ Wrong - no Portal
{/* Renders in place */}
// ✅ Correct - explicit Portal
```
### Issue #6: Arrow Component Not Styled
**Error**: Arrow is invisible
**Source**: https://github.com/mui/base-ui/issues/456
**Why It Happens**: Arrow requires explicit styling (no defaults)
**Prevention**:
```tsx
// ❌ Wrong - invisible arrow
// ✅ Correct - styled arrow
(
)}
/>
```
### Issue #7: Content vs Popup Naming
**Error**: "Property 'Content' does not exist on Dialog"
**Source**: Radix migration issue
**Why It Happens**: Radix uses `Content`, Base UI uses `Popup`
**Prevention**:
```tsx
// ❌ Wrong - Radix naming
...
// ✅ Correct - Base UI naming
...
```
### Issue #8: Overlay vs Backdrop Naming
**Error**: "Property 'Overlay' does not exist on Dialog"
**Source**: Radix migration issue
**Why It Happens**: Radix uses `Overlay`, Base UI uses `Backdrop`
**Prevention**:
```tsx
// ❌ Wrong - Radix naming
// ✅ Correct - Base UI naming
```
### Issue #9: Disabled Button Tooltip Not Showing
**Error**: Tooltip doesn't show on disabled buttons
**Source**: https://github.com/mui/base-ui/issues/567
**Why It Happens**: Disabled elements don't fire pointer events
**Prevention**:
```tsx
// ❌ Wrong - tooltip won't show
} />
// ✅ Correct - wrap in span
(
)} />
```
### Issue #10: Select with Empty String Value
**Error**: Screen reader doesn't announce selected value
**Source**: https://github.com/mui/base-ui/issues/678
**Why It Happens**: Empty string breaks ARIA labeling
**Prevention**:
```tsx
// ❌ Wrong - empty string
Any
// ✅ Correct - sentinel value
Any
```
---
## Critical Rules
### Always Do
✅ **Spread props from render functions** - `