base-ui-react

安装量: 121
排名: #7084

安装

npx skills add https://github.com/jackspace/claudeskillz --skill base-ui-react

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 ( {/ Render prop pattern - Base UI's key feature /} ( )} />

  <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 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";

} />

{/ 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>

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";

Base UI (render prop):

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) // ❌ This won't position correctly {/ Missing positioning logic /}

With Positioner (Correct) // ✅ Positioner handles Floating UI positioning

Positioning Options

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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ 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:

// ❌ Wrong - tooltip won't show )} />

  <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 w-full max-w-md"
        >
          <Dialog.Title
            render={(titleProps) => (
              <h2 {...titleProps} className="text-2xl font-bold mb-4">
                Enter Your Name
              </h2>
            )}
          />

          <form onSubmit={handleSubmit}>
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              className="w-full px-3 py-2 border rounded mb-4"
              autoFocus
            />

            <div className="flex justify-end gap-2">
              <Dialog.Close
                render={(closeProps) => (
                  <button {...closeProps} type="button" className="px-4 py-2 border rounded">
                    Cancel
                  </button>
                )}
              />
              <button type="submit" className="px-4 py-2 bg-blue-600 text-white rounded">
                Submit
              </button>
            </div>
          </form>
        </div>
      )}
    />
  </Dialog.Portal>
</Dialog.Root>

); }

When to use: Forms in modals, user input dialogs

Pattern 2: Searchable Select 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 ( ( )} />

  <Select.Positioner side="bottom" alignment="start">
    <Select.Portal>
      <Select.Popup
        render={(props) => (
          <div {...props} className="w-64 bg-white border rounded shadow-lg">
            <div className="p-2 border-b">
              <input
                type="text"
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                placeholder="Search..."
                className="w-full px-3 py-2 border rounded"
              />
            </div>
            <div className="max-h-60 overflow-y-auto">
              {filtered.map((option) => (
                <Select.Option
                  key={option.value}
                  value={option.value}
                  render={(optionProps) => (
                    <div
                      {...optionProps}
                      className="px-4 py-2 cursor-pointer hover:bg-gray-100 data-[selected]:bg-blue-600 data-[selected]:text-white"
                    >
                      {option.label}
                    </div>
                  )}
                />
              ))}
            </div>
          </div>
        )}
      />
    </Select.Portal>
  </Select.Positioner>
</Select.Root>

); }

When to use: Long option lists, type-ahead filtering

Pattern 3: Number Field with Currency Formatting import { NumberField } from "@base-ui-components/react/number-field"; import { useState } from "react";

export function CurrencyInput() { const [price, setPrice] = useState(9.99);

return (

( )} />

    <div className="flex items-center gap-2">
      <NumberField.Decrement
        render={(props) => (
          <button {...props} className="w-8 h-8 bg-gray-200 rounded">
            −
          </button>
        )}
      />

      <NumberField.Input
        render={(props) => (
          <input
            {...props}
            className="w-32 px-3 py-2 text-center border rounded"
          />
        )}
      />

      <NumberField.Increment
        render={(props) => (
          <button {...props} className="w-8 h-8 bg-gray-200 rounded">
            +
          </button>
        )}
      />
    </div>
  </div>
</NumberField.Root>

); }

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:

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:

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:

asChild → render prop

// Radix } />

// ✅ Correct } />

Problem: Popup appearing at wrong position

Solution: Wrap in Positioner:

// ❌ Wrong

// ✅ Correct

Problem: TypeScript error "Property 'align' does not exist"

Solution: Use alignment not align:

// ❌ Wrong (Radix)

// ✅ Correct (Base UI)

Problem: Arrow is invisible

Solution: Style the arrow explicitly:

// ❌ Wrong

// ✅ Correct (

)} />

Problem: Tooltip not showing on disabled button

Solution: Wrap button in span:

// ❌ Wrong

返回排行榜