base-ui-react

from jackspace/claudeskillz

ClaudeSkillz: For when you need skills, but lazier

8 stars2 forksUpdated Nov 20, 2025
npx skills add https://github.com/jackspace/claudeskillz --skill base-ui-react

SKILL.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
  • 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:

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>

...

Read full content

Repository Stats

Stars8
Forks2
LicenseMIT License