Claude
Skills
Sign in
Back

base-ui-react

Included with Lifetime
$97 forever

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

Design

What this skill does


# 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/[email protected]

---

## ⚠️ 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 (
    <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
- ✅ `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";

<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)**:
```tsx
import * as Dialog from "@radix-ui/react-dialog";

<Dialog.Trigger asChild>
  <button>Open</button>
</Dialog.Trigger>
```

**Base UI (render prop)**:
```tsx
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)
```tsx
// ❌ This won't position correctly
<Popover.Root>
  <Popover.Trigger />
  <Popover.Popup /> {/* Missing positioning logic */}
</Popover.Root>
```

### With Positioner (Correct)
```tsx
// ✅ Positioner handles Floating UI positioning
<Popover.Root>
  <Popover.Trigger />
  <Popover.Positioner side="top" alignment="center">
    <Popover.Portal>
      <Popover.Popup />
    </Popover.Portal>
  </Popover.Positioner>
</Popover.Root>
```

### Positioning Options

```typescript
<Positioner
  side="top"          // top | right | bottom | left
  alignment="center"  // start | center | end
  sideOffset={8}      // Gap between trigger and popup
  alignmentOffset={0} // Shift along alignment axis
  collisionBoundary={null} // null = viewport, or HTMLElement
  collisionPadding={8}     // Padding from boundary
/>
```

---

## 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
<Trigger render={() => <button>Click</button>} />

// ✅ Correct - props spread
<Trigger render={(props) => <button {...props}>Click</button>} />
```

### 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
<Popover.Root>
  <Popover.Trigger />
  <Popover.Popup />
</Popover.Root>

// ✅ Correct - Positioner handles positioning
<Popover.Root>
  <Popover.Trigger />
  <Popover.Positioner>
    <Popover.Portal>
      <Popover.Popup />
    </Popover.Portal>
  </Popover.Positioner>
</Popover.Root>
```

### 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
<Positioner align="center" />

// ✅ Correct - Base UI API
<Positioner alignment="center" />
```

### 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
<Trigger asChild>
  <button>Click</button>
</Trigger>

// ✅ Correct - Base UI pattern
<Trigg
Files: 4
Size: 94.8 KB
Complexity: 49/100
Category: Design

Related in Design