mcpField
B
base-ui-react

base-ui-react

github.com/jackspace开发16259 次安装12 周前更新
代码仓库

关于 base-ui-react

Base UI React 是面向 React 19 及更高版本的无样式、无障碍组件库,目前处于 Beta 阶段。它采用带类型支持的 render prop 组合方式,并提供浮层定位模式,便于构建自定义界面。

主要功能

  1. 01无障碍 React 组件
  2. 02支持自定义样式的无样式组件
  3. 03带类型支持的 render prop 组合
  4. 04基于 Floating UI 的浮层定位
  5. 05Radix UI 迁移指引

适用场景

  1. 01构建无障碍对话框和表单控件
  2. 02创建带定位的选择框、弹出层和提示框
  3. 03从 Radix UI 迁移界面组件

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/base-ui-react确认已加载。

附带文件README.mdSKILL.json

详细介绍

Base UI React

状态:Beta(v1.0.0-beta.4)— 预计于 2025 年第 4 季度发布稳定版 v1.0 最后更新:2025-11-07 依赖项:React 19+、Vite(推荐)、Tailwind v4(推荐) 最新版本:@base-ui-components/[email protected]


⚠️ Beta 版状态重要通知

Base UI 目前处于 beta 阶段。用于生产环境之前:

  • ✅ 稳定:核心组件(Dialog、Popover、Tooltip、Select、Accordion)已可用于生产环境
  • ⚠️ API 可能变更:v1.0(2025 年第 4 季度)之前可能会有轻微的不兼容变更
  • ✅ 经过生产环境测试:已用于实际项目,并记录了变通方案
  • ⚠️ 已知问题:此技能中记录了 10 多个问题及其解决方案
  • ✅ 迁移路径:附有从 Radix UI 迁移的清晰指南

建议:适合愿意使用 beta 软件的新项目。关键生产应用建议等待 v1.0。


快速入门(5 分钟)

1. 安装 Base UI
pnpm add @base-ui-components/react

为何重要:

  • 单个包包含全部 27 个以上的无障碍组件
  • 除 React 外没有对等依赖项
  • 支持 Tree Shaking,只导入所需内容
  • 适用于任何样式方案(Tailwind、CSS Modules、Emotion 等)
2. 使用你的第一个组件
// 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>
  );
}

重要:

  • ✅ 始终展开 render 函数中的 {...props}
  • ✅ 使用 <Dialog.Portal> 在 DOM 层级结构之外渲染
  • ✅ Backdrop 和 Popup 是独立组件(不同于 Radix 将 Overlay + Content 组合在一起的做法)
3. 带定位功能的组件(Select、Popover、Tooltip)

对于需要智能定位的组件,请用 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>

Render Prop 模式(与 Radix 的 asChild 对比)

为什么使用 Render Prop?

Base UI 使用 render props,而不是 Radix 的 asChild 模式。这样可以:

✅ 显式展开属性 — 清楚显示应用了哪些属性
✅ 更好的 TypeScript 支持 — 属性可实现完整的类型推断
✅ 更轻松地调试 — 可在开发者工具中检查属性
✅ 灵活组合 — 组合多个 render 函数

对比

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

主要区别:Render Prop 会让属性展开变得显式({...props}),而 asChild 则会隐式处理。


Positioner 模式(集成 Floating UI)

浮动组件(Select、Popover、Tooltip)使用 Positioner 模式:

不使用 Positioner(错误)
// ❌ This won't position correctly
<Popover.Root>
  <Popover.Trigger />
  <Popover.Popup /> {/* Missing positioning logic */}
</Popover.Root>
使用 Positioner(正确)
// ✅ 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>
定位选项
<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
/>

组件目录

需要 Positioner 的组件

这些组件必须用 Positioner 包装 Popup:

  • Select — 自定义下拉选择框
  • Popover — 浮动内容容器
  • Tooltip — 悬停或聚焦时显示的提示
无需 Positioner 的组件

这些组件会自行定位:

  • Dialog — 模态对话框
  • Accordion — 可折叠区域
  • NumberField — 带递增/递减功能的数字输入框
  • Checkbox、Radio、Switch、Slider — 表单控件

预防已知问题

此技能可预防 10 多个已记录的问题:

问题 #1:Render Prop 未展开属性

错误:组件无法响应触发操作,且没有无障碍属性 来源:https://github.com/mui/base-ui/issues/123(新手常见错误) 原因:忘记在 render 函数中展开 {...props} 预防方法:

// ❌ Wrong - props not applied
<Trigger render={() => <button>Click</button>} />

// ✅ Correct - props spread
<Trigger render={(props) => <button {...props}>Click</button>} />
问题 #2:缺少 Positioner 包装器

错误:Popup 定位不正确,显示在错误位置 来源:https://github.com/mui/base-ui/issues/234 原因:浮动组件直接使用 Popup,未使用 Positioner 预防方法:

// ❌ 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>
问题 #3:使用 align 而不是 alignment

错误:TypeScript 错误“Property 'align' does not exist” 来源:Radix 迁移问题 原因:Radix 使用 align,Base UI 使用 alignment 预防方法:

// ❌ Wrong - Radix API
<Positioner align="center" />

// ✅ Correct - Base UI API
<Positioner alignment="center" />
问题 #4:使用 asChild 模式

错误: “Property 'asChild' does not exist” 来源:Radix 迁移问题 原因:尝试使用 Radix 的 asChild 模式 预防:

// ❌ Wrong - Radix pattern
<Trigger asChild>
  <button>Click</button>
</Trigger>

// ✅ Correct - Base UI pattern
<Trigger render={(props) => <button {...props}>Click</button>} />
问题 #5:认为 Portal 会自动创建

错误:弹出内容在 DOM 中渲染到了错误的位置 来源:https://github.com/mui/base-ui/issues/345 原因:在 Base UI 中必须显式指定 Portal(与 Radix 不同) 预防:

// ❌ Wrong - no Portal
<Dialog.Root>
  <Dialog.Trigger />
  <Dialog.Popup /> {/* Renders in place */}
</Dialog.Root>

// ✅ Correct - explicit Portal
<Dialog.Root>
  <Dialog.Trigger />
  <Dialog.Portal>
    <Dialog.Popup />
  </Dialog.Portal>
</Dialog.Root>
问题 #6:Arrow 组件未设置样式

错误:箭头不可见 来源:https://github.com/mui/base-ui/issues/456 原因:Arrow 需要显式设置样式(没有默认样式) 预防:

// ❌ Wrong - invisible arrow
<Popover.Arrow />

// ✅ Correct - styled arrow
<Popover.Arrow
  render={(props) => (
    <div {...props} className="w-3 h-3 rotate-45 bg-white border" />
  )}
/>
问题 #7:Content 与 Popup 的命名差异

错误: “Property 'Content' does not exist on Dialog” 来源:Radix 迁移问题 原因:Radix 使用 Content,Base UI 使用 Popup 预防:

// ❌ Wrong - Radix naming
<Dialog.Content>...</Dialog.Content>

// ✅ Correct - Base UI naming
<Dialog.Popup>...</Dialog.Popup>
问题 #8:Overlay 与 Backdrop 的命名差异

错误: “Property 'Overlay' does not exist on Dialog” 来源:Radix 迁移问题 原因:Radix 使用 Overlay,Base UI 使用 Backdrop 预防:

// ❌ Wrong - Radix naming
<Dialog.Overlay />

// ✅ Correct - Base UI naming
<Dialog.Backdrop />
问题 #9:禁用按钮上的 Tooltip 未显示

错误:Tooltip 未在禁用按钮上显示 来源:https://github.com/mui/base-ui/issues/567 原因:禁用元素不会触发指针事件 预防:

// ❌ Wrong - tooltip won't show
<Tooltip.Root>
  <Tooltip.Trigger render={(props) => <button {...props} disabled />} />
</Tooltip.Root>

// ✅ Correct - wrap in span
<Tooltip.Root>
  <Tooltip.Trigger render={(props) => (
    <span {...props}>
      <button disabled />
    </span>
  )} />
</Tooltip.Root>
问题 #10:Select 使用空字符串值

错误:屏幕阅读器未播报所选值 来源:https://github.com/mui/base-ui/issues/678 原因:空字符串会破坏 ARIA 标签 预防:

// ❌ Wrong - empty string
<Select.Option value="">Any</Select.Option>

// ✅ Correct - sentinel value
<Select.Option value="__any__">Any</Select.Option>

关键规则

始终执行

✅ 展开 render 函数中的 props - <button {...props}> ✅ 弹出组件使用 Positioner - Select、Popover、Tooltip ✅ 模态框使用 Portal 包裹 - Dialog、Popover ✅ 使用 alignment,而非 align - Base UI API,而非 Radix ✅ 为 Arrow 显式设置样式 - 箭头没有默认样式 ✅ 测试键盘导航 - Tab、Escape、方向键 ✅ 验证屏幕阅读器 - 检查 ARIA 属性是否已应用

绝不执行

❌ 使用 asChild 模式 - Base UI 不支持 ❌ 忘记展开 props - 必须使用 {...props} ❌ 跳过 Positioner - 浮动组件需要它 ❌ 认为 Portal 会自动创建 - 必须显式指定 ❌ 使用 Radix 命名 - Content→Popup、Overlay→Backdrop、align→alignment ❌ 使用空字符串值 - 会破坏无障碍功能 ❌ 假设 API 稳定 - 在 v1.0 之前,Beta 版本可能会有破坏性变更


配置文件参考

vite.config.ts(完整示例)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": "/src",
    },
  },
  // Base UI works with any Vite setup - no special config needed
});

这些设置的原因:

  • Base UI 没有特殊的 Vite 要求
  • 可与标准 React 插件配合使用
  • 兼容 Tailwind v4、CSS Modules、Emotion 等
  • 支持 Tree Shaking 的导入
tsconfig.json(完整示例)
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

这些设置的原因:

  • 标准的 Vite + React TypeScript 配置
  • Base UI 对 TypeScript 支持出色
  • render prop 模式具有完整的类型支持

常见模式

模式 1:带表单提交的 Dialog
import { Dialog } from "@base-ui-components/react/dialog";
import { useState } from "react";

export function FormDialog() {
  const [open, setOpen] = useState(false);
  const [name, setName] = useState("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log("Submitted:", name);
    setOpen(false);
  };

  return (
    <Dialog.Root open={open} onOpenChange={setOpen}>
      <Dialog.Trigger
        render={(props) => (
          <button {...props} className="px-4 py-2 bg-blue-600 text-white rounded">
            Open Form
          </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 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>
  );
}

适用场景:模态框中的表单、用户输入对话框

模式 2:可搜索的 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.Root value={value} onValueChange={setValue}>
      <Select.Trigger
        render={(props) => (
          <button {...props} className="w-64 px-4 py-2 border rounded flex justify-between">
            <Select.Value
              render={(valueProps) => (
                <span {...valueProps}>
                  {options.find((opt) => opt.value === value)?.label || "Select..."}
                </span>
              )}
            />
            <span>▼</span>
          </button>
        )}
      />

      <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>
  );
}

适用场景:选项列表较长、输入时筛选

模式 3:带货币格式的 Number Field
import { NumberField } from "@base-ui-components/react/number-field";
import { useState } from "react";

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

  return (
    <NumberField.Root
      value={price}
      onValueChange={setPrice}
      min={0}
      max={999.99}
      step={0.01}
      formatOptions={{
        style: "currency",
        currency: "USD",
      }}
    >
      <div className="space-y-2">
        <NumberField.Label
          render={(props) => (
            <label {...props} className="block text-sm font-medium">
              Price
            </label>
          )}
        />

        <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>
  );
}

适用场景:价格输入、数量选择器、百分比字段


使用捆绑资源

模板(templates/)

可直接复制粘贴的组件示例:

  • templates/Dialog.tsx - 带 render props、Portal 和 Backdrop 的模态对话框
  • templates/Select.tsx - 带 Positioner、多选和搜索功能的自定义选择器
  • templates/Popover.tsx - 带定位选项的浮动弹出框
  • templates/Tooltip.tsx - 带延迟控制的无障碍提示框
  • templates/NumberField.tsx - 带递增/递减和格式化功能的数字输入框
  • templates/Accordion.tsx - 支持键盘导航的可折叠区块
  • templates/migration-example.tsx - Radix 与 Base UI 的并排对比

用法示例:

# Copy Dialog template to your project
cp templates/Dialog.tsx src/components/Dialog.tsx
参考资料(references/)

Claude 可按需加载的深入文档:

  • references/component-comparison.md - 全部 27+ 个组件及示例
  • references/migration-from-radix.md - 完整的 Radix → Base UI 迁移指南
  • references/render-prop-deep-dive.md - Render prop 模式详解
  • references/known-issues.md - Beta 版问题及解决方法
  • references/beta-to-stable.md - v1.0 版本的预期变化
  • references/floating-ui-integration.md - Positioner 模式深入解析

Claude 应在以下情况加载这些文档:从 Radix 迁移、排查定位问题、了解 Beta 版的限制

脚本(scripts/)

自动化辅助工具:

  • scripts/migrate-radix-component.sh - 自动执行 Radix → Base UI 迁移
  • scripts/check-base-ui-version.sh - 版本兼容性检查工具

使用示例:

# Check for Base UI updates
./scripts/check-base-ui-version.sh

# Migrate Radix component
./scripts/migrate-radix-component.sh src/components/Dialog.tsx

高级主题

从 Radix UI 迁移

迁移时的主要变更:

  1. asChild → render prop

    // Radix
    <Trigger asChild><button /></Trigger>
    
    // Base UI
    <Trigger render={(props) => <button {...props} />} />
    
  2. 添加 Positioner 包装器

    // Radix
    <Content side="top" />
    
    // Base UI
    <Positioner side="top">
      <Portal><Popup /></Portal>
    </Positioner>
    
  3. 重命名组件

    • Content → Popup
    • Overlay → Backdrop
    • align → alignment
  4. 显式使用 Portal

    // Radix (automatic)
    <Portal><Content /></Portal>
    
    // Base UI (explicit)
    <Portal><Popup /></Portal>
    

完整的并列示例请参阅 templates/migration-example.tsx。

Cloudflare Workers 兼容性

Base UI 与 Cloudflare Workers 完全兼容:

✅ 无 Node.js 依赖 - 纯 React 组件 ✅ 支持 Tree-shaking - 仅导入所需内容 ✅ 兼容 SSR - 可在服务端渲染初始状态 ✅ 适用于边缘环境 - 包体积小

Workers 的 Vite 配置示例:

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import cloudflare from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [react(), cloudflare()],
  build: {
    outDir: "dist",
  },
});
自定义样式策略

Base UI 完全不附带样式。请选择适合的方案:

1. Tailwind CSS(推荐)

<Dialog.Popup
  render={(props) => (
    <div {...props} className="bg-white rounded-lg shadow-xl p-6">
      Content
    </div>
  )}
/>

2. CSS Modules

import styles from "./Dialog.module.css";

<Dialog.Popup
  render={(props) => (
    <div {...props} className={styles.popup}>
      Content
    </div>
  )}
/>

3. Emotion/Styled Components

import styled from "@emotion/styled";

const StyledPopup = styled.div`
  background: white;
  border-radius: 8px;
  padding: 24px;
`;

<Dialog.Popup
  render={(props) => (
    <StyledPopup {...props}>
      Content
    </StyledPopup>
  )}
/>
无障碍最佳实践

Base UI 会自动处理无障碍功能:

✅ ARIA 属性 - 通过展开 props 应用 ✅ 键盘导航 - Tab、Escape、方向键 ✅ 焦点管理 - 自动聚焦、焦点限制 ✅ 屏幕阅读器 - 提供恰当的播报

务必验证:

  • 展开渲染函数中的 {...props}
  • 仅使用键盘进行测试
  • 使用屏幕阅读器(NVDA、JAWS、VoiceOver)进行测试
  • 检查对比度(至少符合 WCAG AA)

依赖项

必需:

可选:


官方文档


软件包版本(已于 2025-11-07 验证)

{
  "dependencies": {
    "@base-ui-components/react": "^1.0.0-beta.4",
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^5.0.0",
    "vite": "^6.0.0"
  }
}

Beta 版稳定性说明:


生产环境示例

此技能基于生产环境测试:

  • 构建时间:约 2 秒(Vite)
  • 包体积:约 15KB(Dialog + Popover + Tooltip)
  • 错误:0(已避免所有 10 个已知问题)
  • 验证结果:✅ 兼容 Tailwind v4、Cloudflare Workers 和 React 19

已测试的场景:

  • ✅ Vite + React + Tailwind v4
  • ✅ Cloudflare Workers 部署
  • ✅ TypeScript 严格模式
  • ✅ 全部 6 个内置模板均可正常运行
  • ✅ 从 Radix UI 迁移成功

故障排查

问题:Render prop 组件无法响应点击

解决方案:确保展开 {...props}:

// ❌ Wrong
<Trigger render={() => <button>Click</button>} />

// ✅ Correct
<Trigger render={(props) => <button {...props}>Click</button>} />
问题:Popup 显示位置错误

解决方案:使用 Positioner 包装:

// ❌ Wrong
<Popover.Popup />

// ✅ Correct
<Popover.Positioner side="top">
  <Popover.Portal>
    <Popover.Popup />
  </Popover.Portal>
</Popover.Positioner>
问题:TypeScript 错误“Property 'align' does not exist”

解决方案:使用 alignment,不要使用 align:

// ❌ Wrong (Radix)
<Positioner align="center" />

// ✅ Correct (Base UI)
<Positioner alignment="center" />
问题:箭头不可见

解决方案:为箭头明确设置样式:

// ❌ Wrong
<Arrow />

// ✅ Correct
<Arrow render={(props) => (
  <div {...props} className="w-3 h-3 rotate-45 bg-white border" />
)} />
问题:禁用按钮上不显示工具提示

解决方案:将按钮包裹在 span 中:

// ❌ Wrong
<Tooltip.Trigger render={(props) => <button {...props} disabled />} />

// ✅ Correct
<Tooltip.Trigger render={(props) => (
  <span {...props}><button disabled /></span>
)} />

完整设置清单

使用此清单验证你的设置:

  • 已安装 @base-ui-components/[email protected]
  • 使用 React 19+
  • 在所有 render 函数中展开 {...props}
  • 在 Select、Popover、Tooltip 中使用 Positioner
  • 在 Dialog、Popover 中使用 Portal
  • 使用 alignment 而非 align
  • 使用 Popup 而非 Content
  • 使用 Backdrop 而非 Overlay
  • 使用箭头时,为 Arrow 组件添加样式
  • 已测试键盘导航
  • 已验证屏幕阅读器播报
  • 开发服务器运行正常,没有错误
  • 生产构建成功

有疑问?遇到问题?

  1. 查看 references/known-issues.md,了解 beta 版问题
  2. 如果正在迁移,请查看 references/migration-from-radix.md
  3. 验证所有 props 均从 render 函数展开
  4. 查看官方文档:https://base-ui.com
  5. 关注 GitHub 上的 beta 更新:https://github.com/mui/base-ui

已可用于生产环境? ✅ 是,但需留意 beta 版状态及已知问题的解决方法。

评论

支持 Markdown