--- 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 )} />
} /> (
(

Enter Your Name

)} />
setName(e.target.value)} className="w-full px-3 py-2 border rounded mb-4" autoFocus />
( )} />
)} /> ); } ``` **When to use**: Forms in modals, user input dialogs ### Pattern 2: Searchable Select ```typescript import { Select } from "@base-ui-components/react/select"; import { useState } from "react"; const options = [ { value: "react", label: "React" }, { value: "vue", label: "Vue" }, { value: "angular", label: "Angular" }, ]; export function SearchableSelect() { const [value, setValue] = useState(""); const [search, setSearch] = useState(""); const filtered = options.filter((opt) => opt.label.toLowerCase().includes(search.toLowerCase()) ); return ( ( )} /> (
setSearch(e.target.value)} placeholder="Search..." className="w-full px-3 py-2 border rounded" />
{filtered.map((option) => ( (
{option.label}
)} /> ))}
)} />
); } ``` **When to use**: Long option lists, type-ahead filtering ### Pattern 3: Number Field with Currency Formatting ```typescript import { NumberField } from "@base-ui-components/react/number-field"; import { useState } from "react"; export function CurrencyInput() { const [price, setPrice] = useState(9.99); return (
( )} />
( )} /> ( )} /> ( )} />
); } ``` **When to use**: Price inputs, quantity selectors, percentage fields --- ## Using Bundled Resources ### Templates (templates/) Copy-paste ready component examples: - `templates/Dialog.tsx` - Modal dialog with render props, Portal, Backdrop - `templates/Select.tsx` - Custom select with Positioner, multi-select, searchable - `templates/Popover.tsx` - Floating popover with positioning options - `templates/Tooltip.tsx` - Accessible tooltip with delay controls - `templates/NumberField.tsx` - Number input with increment/decrement, formatting - `templates/Accordion.tsx` - Collapsible sections with keyboard navigation - `templates/migration-example.tsx` - Side-by-side Radix vs Base UI comparison **Example Usage:** ```bash # Copy Dialog template to your project cp templates/Dialog.tsx src/components/Dialog.tsx ``` ### References (references/) Deep-dive documentation Claude can load when needed: - `references/component-comparison.md` - All 27+ components with examples - `references/migration-from-radix.md` - Complete Radix → Base UI migration guide - `references/render-prop-deep-dive.md` - Render prop pattern explained - `references/known-issues.md` - Beta bugs and workarounds - `references/beta-to-stable.md` - What to expect in v1.0 - `references/floating-ui-integration.md` - Positioner pattern deep-dive **When Claude should load these**: Migrating from Radix, troubleshooting positioning issues, understanding beta limitations ### Scripts (scripts/) Automation helpers: - `scripts/migrate-radix-component.sh` - Automated Radix → Base UI migration - `scripts/check-base-ui-version.sh` - Version compatibility checker **Example Usage:** ```bash # Check for Base UI updates ./scripts/check-base-ui-version.sh # Migrate Radix component ./scripts/migrate-radix-component.sh src/components/Dialog.tsx ``` --- ## Advanced Topics ### Migrating from Radix UI Key changes when migrating: 1. **asChild → render prop** ```tsx // Radix } /> // ✅ Correct } /> ``` ### Problem: Popup appearing at wrong position **Solution**: Wrap in Positioner: ```tsx // ❌ Wrong // ✅ Correct ``` ### Problem: TypeScript error "Property 'align' does not exist" **Solution**: Use `alignment` not `align`: ```tsx // ❌ Wrong (Radix) // ✅ Correct (Base UI) ``` ### Problem: Arrow is invisible **Solution**: Style the arrow explicitly: ```tsx // ❌ Wrong // ✅ Correct (
)} /> ``` ### Problem: Tooltip not showing on disabled button **Solution**: Wrap button in span: ```tsx // ❌ Wrong