shadcn/ui 迁移指南:Radix UI 到 Base UI 的 progress、scroll-area、separator、avatar、toast、form Props 完整映射
本文是 shadcn/ui 仓库内 Radix → Base UI 迁移技能包(skills/migrate-radix-to-base/)中「展示类与杂项原语」一族的 Props 映射参考的完整解读。它将逐组件、逐 Prop 地给出 Radix UI 与 Base UI(@base-ui/react,原 @base-ui-components/react)之间的部件映射、参数改名/删除清单、data 属性与 CSS 变量改写规则,并结合本仓库 registry 中现有的 Radix 组件源码,帮助你在实际项目中完成 progress、scroll-area、separator、avatar、toast、form 六个组件族的迁移,以及对 Label、AspectRatio、VisuallyHidden、AccessibleIcon 四个「无对应物」工具类原语的降级替换。
一、文档定位:迁移知识库中的 per-family props 表
本映射表位于 skills/migrate-radix-to-base/display-misc.md,它是 SKILL.md 定义的 Agent 迁移技能中「转换引擎(transformation engine)」的六份分族 props 参考表之一(其余为 overlays.md、menus.md、form-controls.md、disclosure.md、universal-patterns.md)。SKILL.md 明确要求:绝不猜测映射,表中没有的 Prop 必须去查 node_modules/@base-ui/react/**/*.d.ts,并把缺口记录进迁移报告。
映射数据的来源在文档头部即有声明:Radix 侧取自 radix-ui 官网文档 data/primitives/docs/components/*.mdx 的完整内联 props 表,Base 侧取自 base-ui.com 各组件文档(抓取于 2026-07-02,对应 @base-ui/react 包)。姊妹文档 universal-patterns.md 补充说明:整库的知识基线是对 apps/v4/registry/bases/{radix,base}/ui/ 下 61 组组件对的机械 diff(first-party ground truth)加上 radix-ui@1.4.3 的包导出清单构建的,因此本文表格可直接作为迁移时的「查询词典」使用。
二、通用约定(适用于下文所有组件)
文档头部给出三条「通用约定」,对下文每一张表都成立,不再逐表重复:
asChild(boolean)→render,类型是ReactElement | ((props: HTMLProps, state) => ReactElement)。注意签名变了,调用形态从「包裹子元素」变成「传渲染元素」:
// Radix
<Part asChild><a href="..." /></Part>
// Base UI
<Part render={<a href="..." />} />
对于手动 Slot 惯用法(const Comp = asChild ? Slot.Root : "a"),universal-patterns.md 给出了替代方案:useRender + mergeProps(来自 @base-ui/react/use-render / @base-ui/react/merge-props),并附了 breadcrumb-link 的完整 worked example;注意其中提到的坑:传入 mergeProps 的对象字面量里含 data-* 键时需要 as React.ComponentProps<"tag"> 断言,否则 tsc 报 excess-property 错误。
-
Base UI 的
className和style都接受函数,参数是该部件的State对象。这是 data 属性改写之后「按状态写样式」的主力手段。 -
每个 Base 部件都暴露
Part.Props与Part.State类型(例如Progress.Root.Props)。迁移 shadcn 包装器时,类型标注从React.ComponentProps<typeof XPrimitive.Root>改为XPrimitive.Root.Props。
此外,通用模式表中与本文各组件直接相关的全局改写规则还有:data-[state=open] → data-open(布尔存在式属性取代枚举值);进出场动画从 data-[state=open]:animate-in / data-[state=closed]:animate-out(keyframes)改写为 data-starting-style:* / data-ending-style:*(transition-based)。下文各组件的 data 属性表都是在这条全局规则之上的特化。
三、progress:Indicator 必须嵌套进新 Track,删除手动 translateX
3.1 部件映射
Progress.Root → Progress.Root,Progress.Indicator → Progress.Indicator,但 Indicator 现在必须嵌套在新的 Progress.Track 内。Base UI 新增 Track、Label、Value 三个部件。关键行为差异:Base UI 由原语自己计算 Indicator 的填充宽度(内联 style),因此 Radix 侧「在 Indicator 上写 style={{ transform: translateX(-(100 - value)%) }}」的惯用 hack 应删除,而不是搬运。
这一条在本仓库中可以直接对证——现有 Radix 版包装器 apps/v4/registry/new-york-v4/ui/progress.tsx 正是这个 hack:
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
迁移到 Base UI 时,这行 style 连同对 value 的手动解构一起删除,宽度交由原语负责。
3.2 Progress.Root → Progress.Root
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更(见第二节)。 |
value |
number | null / - |
value |
语义相同。Base UI 中为必填(默认 null)。两边 null 都表示 indeterminate。 |
max |
number / - |
max |
相同。Base UI 默认 100;并新增 min(默认 0)。 |
getValueLabel |
(value: number, max: number) => string / - |
getAriaValueText |
改名 + 签名变更:Base UI 是 (formattedValue: string | null, value: number | null) => string。百分比计算逻辑消失,格式化改用 format/locale。 |
3.3 Progress.Indicator → Progress.Indicator
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
两侧唯一 Prop。嵌套进 Progress.Track;宽度由原语设置。 |
3.4 Base UI 独有、值得了解的 props
- Root:
min(0)、format(Intl.NumberFormatOptions)、locale(Intl.LocalesArgument)、aria-valuetext。 - 新部件:
Progress.Track(包含 Indicator)、Progress.Label(无障碍标签,渲染<span>)、Progress.Value(格式化值文本,渲染<span>,children为渲染函数(formattedValue, value) => ReactNode)。
3.5 Data 属性与 CSS 变量
| Radix | Base UI |
|---|---|
[data-state="loading"] |
[data-progressing](布尔存在式属性取代枚举) |
[data-state="complete"] |
[data-complete] |
[data-state="indeterminate"] |
[data-indeterminate] |
[data-value]、[data-max] |
删除。需要时读 className/style 状态函数里的 value,或自行设置属性。 |
上述 Base 属性同时出现在 Root、Track、Indicator、Label、Value 上,State 类型为 { status: 'indeterminate' | 'progressing' | 'complete' }。CSS 变量:两侧均无。
四、scroll-area:Scrollbar/Thumb 改名,可见性从 props 变为 CSS 驱动
4.1 部件映射
ScrollArea.Root → ScrollArea.Root,ScrollArea.Viewport → ScrollArea.Viewport,ScrollAreaScrollbar → ScrollArea.Scrollbar,ScrollAreaThumb → ScrollArea.Thumb,ScrollArea.Corner → ScrollArea.Corner。Base UI 新增 ScrollArea.Content(包在 Viewport 内部,用于水平溢出测量)。本仓库现有实现 apps/v4/registry/new-york-v4/ui/scroll-area.tsx 展示了典型的 Radix 组装形态(Root > Viewport + 自定义 ScrollBar + Corner),迁移时保留该结构,仅重命名 Scrollbar/Thumb 两个部件。
4.2 ScrollArea.Root → ScrollArea.Root
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
type |
"auto" | "always" | "scroll" | "hover" / "hover" |
删除 | 可见性改为 CSS 驱动:基于 [data-hovering]/[data-scrolling] 给 Scrollbar 写 opacity(对应 hover/scroll 行为),或写始终可见的 CSS 对应 "always"(配合 Scrollbar 的 keepMounted)。"auto" 是默认挂载行为(可滚动时才挂载 scrollbar)。 |
scrollHideDelay |
number / 600 |
删除 | 用 scrollbar opacity 过渡上的 CSS transition-delay 复现。 |
dir |
"ltr" | "rtl" / - |
删除 | Base UI 从 DOM(dir 属性)/ 其 DirectionProvider 工具读取方向;无组件级 prop。 |
nonce |
string / - |
删除 | 无文档化的 CSP nonce 对应项。 |
4.3 其余部件
| 部件 | 映射 | 要点 |
|---|---|---|
ScrollArea.Viewport |
ScrollArea.Viewport |
仅 asChild → render。角色相同(可滚动容器)。水平滚动重要时,把 children 包进 ScrollArea.Content。 |
ScrollAreaScrollbar |
ScrollArea.Scrollbar |
asChild → render;forceMount → keepMounted(改名,boolean,默认 false,不可滚动时也保留在 DOM 中);orientation 相同、默认同为 "vertical"。 |
ScrollAreaThumb |
ScrollArea.Thumb |
两侧都只有 asChild → render。 |
ScrollArea.Corner |
ScrollArea.Corner |
同上。 |
Base UI 独有、值得了解的:Root 的 overflowEdgeThreshold(number | Partial<{ xStart; xEnd; yStart; yEnd }>,默认 0),溢出边缘属性翻转的阈值;新部件 ScrollArea.Content(Viewport 内的 div,与 Root 相同的 overflow data 属性)。
4.4 Data 属性与 CSS 变量
| Radix | Base UI |
|---|---|
Scrollbar [data-state="visible" | "hidden"] |
删除。用 Scrollbar 上的 [data-hovering]、[data-scrolling]、[data-has-overflow-x/y] 驱动可见性样式。 |
Scrollbar/Thumb [data-orientation] |
相同(Scrollbar 与 Thumb 上的 data-orientation)。 |
| - | 新增(Root/Content/Viewport/Scrollbar 上):data-has-overflow-x、data-has-overflow-y、data-overflow-x-start/end、data-overflow-y-start/end、data-scrolling;Scrollbar 另有 data-hovering。 |
Radix 文档未列 CSS 变量(其实现带有未文档化的 --radix-scroll-area-thumb-*/corner-*)。Base UI 文档化的变量:
| Base UI 变量 | 所在部件 |
|---|---|
--scroll-area-corner-width、--scroll-area-corner-height |
Root |
--scroll-area-thumb-width、--scroll-area-thumb-height |
Scrollbar |
--scroll-area-overflow-x-start/end、--scroll-area-overflow-y-start/end |
Viewport(距各边缘的像素距离,非常适合做滚动渐隐遮罩) |
五、separator:变为可调用单部件,decorative 删除
部件映射:Separator.Root → Separator(可调用单部件,没有 .Root)。
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
orientation |
"horizontal" | "vertical" / "horizontal" |
orientation |
相同,默认相同(Orientation 类型)。 |
decorative |
boolean / - |
删除 | Base UI 的 separator 恒为语义元素(role="separator")。纯视觉分隔线请渲染普通 <div aria-hidden="true"> 或直接用 CSS 边框。 |
Base UI 独有点:除通用 className/style/render 外无额外 prop,渲染 <div>。Data 属性:[data-orientation](horizontal | vertical)两侧一致。CSS 变量:两侧均无。
一个值得注意的仓库现状:现有包装器 apps/v4/registry/new-york-v4/ui/separator.tsx 默认传了 decorative = true。迁移时这一默认值要一并处理——Base 侧没有该开关,「默认纯装饰」的项目行为需要改为「默认语义分隔」,属于 SKILL.md 所说的「行为差异要标记(flag)、不得静默修补」的典型场景。
六、avatar:解剖结构不变,两处改名
部件映射:Avatar.Root → Avatar.Root,Avatar.Image → Avatar.Image,Avatar.Fallback → Avatar.Fallback,解剖结构相同。Base 侧 Root 渲染 <span>、Image 渲染 <img>、Fallback 渲染 <span>。
| 部件 | Radix prop | Base UI 对应 | 迁移说明 |
|---|---|---|---|
| Root | asChild |
render |
两侧唯一 Prop。Base Root 也可以直接接受普通 children(例如首字母)而不使用 Image/Fallback。 |
| Image | asChild / onLoadingStatusChange |
render / onLoadingStatusChange |
回调同名同类型("idle" | "loading" | "loaded" | "error" 的 ImageLoadingStatus 联合)。 |
| Fallback | asChild / delayMs |
render / delay |
delayMs 改名为 delay,含义相同(显示 fallback 前等待的毫秒数)。 |
Base UI 独有点:除通用三件套外无新 prop。部件 State 暴露 imageLoadingStatus(Image 上还有 transitionStatus),供 className/style 函数使用。Data 属性:Radix 未文档化任何属性;Base UI Image 新增 data-starting-style / data-ending-style 用于进出场过渡。CSS 变量:两侧均无。仓库现有实现 apps/v4/registry/new-york-v4/ui/avatar.tsx 是「Radix 原语 + data-slot + 尺寸分组」的 shadcn 典型组装,迁移时仅需替换 import 与 asChild 用法,data-slot 命名与 className 体系可原样保留。
七、toast:心智模型整体切换——从声明式到 manager 驱动
这是六个组件族中变化最彻底的一个。Radix toast 是声明式的(你自己渲染 <Toast.Root open>);Base UI toast 是 manager 驱动的:toast 通过 Toast.useToastManager().add({ title, description, ... }) 命令式创建(或使用全局 Toast.createToastManager() 并传给 Provider 的 toastManager),然后在 Viewport 内渲染 useToastManager().toasts.map((toast) => <Toast.Root key={toast.id} toast={toast} />)。
7.1 部件映射
| Radix 部件 | Base UI 部件 |
|---|---|
Toast.Provider |
Toast.Provider(props 差异很大) |
Toast.Viewport |
Toast.Portal + Toast.Viewport(Portal 为新增,默认追加到 <body>) |
Toast.Root |
Toast.Root(需要 toast 对象;通常包裹新增的 Toast.Content) |
Toast.Title |
Toast.Title(渲染 <h2>) |
Toast.Description |
Toast.Description(渲染 <p>) |
Toast.Action |
Toast.Action(按 toast 渲染;props 可来自 toast.actionProps) |
Toast.Close |
Toast.Close |
| - | 新增:Toast.Content、Toast.Positioner + Toast.Arrow(锚定式 toast)、Toast.createToastManager、Toast.useToastManager |
7.2 Toast.Provider → Toast.Provider
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
duration |
number / 5000 |
timeout |
改名。默认值相同(5000,0 禁用自动消失)。按 toast 覆盖移到 add({ timeout })。 |
label(必填) |
string / "Notification" |
删除 | Base UI 内部处理读屏播报;按 toast 的紧迫度用 add() 中的 priority: 'low' | 'high'。 |
swipeDirection |
"right" | "left" | "up" | "down" / "right" |
移走 | 现为 Toast.Root 上的 swipeDirection;接受单值或数组,默认 ['down', 'right']。 |
swipeThreshold |
number / 50 |
删除 | 不可配置。用 data-base-ui-swipe-ignore 属性让元素退出 swipe。 |
announcerContainer |
Element | DocumentFragment / document.body |
删除 | 最接近的对应是 Toast.Portal 的 container(决定 viewport 渲染位置)。 |
7.3 Toast.Viewport → Toast.Portal + Toast.Viewport
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
Portal 与 Viewport 上均有。 |
hotkey |
string[] / ["F8"] |
删除 | Base UI 硬编码 F6 聚焦 viewport landmark,不可配置。 |
label |
string / "Notifications ({hotkey})" |
删除 | landmark 标签内部处理。 |
Base UI 独有:Portal.container(HTMLElement | ShadowRoot | ref | null)。
7.4 Toast.Root → Toast.Root(删除最多的表格)
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
type |
"foreground" | "background" / "foreground" |
add() 选项中的 priority |
移走 + 改名:foreground ≈ priority: 'high'(紧急播报),background ≈ 'low'(默认)。注意:Base UI 的 toast.type 是另一个概念(自由样式类别,如 'success',以 data-type 暴露)。 |
duration |
number / - |
add() 选项中的 timeout |
移走 + 改名;按 toast 覆盖 Provider 的 timeout。 |
defaultOpen |
boolean / true |
删除 | 打开状态在 manager 中。add() 创建,close(id) 移除。 |
open |
boolean / - |
删除 | 同上;不存在受控打开模式。add({ id }) 会就地 upsert 已存在的 toast。 |
onOpenChange |
(open: boolean) => void / - |
删除(有替代) | 用 toast 对象(add() 选项)中的 onClose / onRemove 回调。 |
onEscapeKeyDown |
(event: KeyboardEvent) => void / - |
删除 | 焦点在 viewport 内时 Esc 关闭仍可用,但不可拦截。 |
onPause / onResume |
() => void / - |
删除 | 定时器仍会在 hover、focus、window blur 时自动暂停,但没有回调。 |
onSwipeStart / onSwipeMove / onSwipeEnd / onSwipeCancel |
(event: SwipeEvent) => void / - |
删除(有替代) | Swipe 是「样式化而非脚本化」:[data-swiping]、[data-swipe-direction] 与 --toast-swipe-movement-x/y 取代事件钩子。 |
forceMount |
boolean / - |
删除 | Root 从 toasts 数组渲染;退出动画在移除前得到 data-ending-style + toast.transitionStatus: 'ending'(之后触发 onRemove)。 |
Base UI 独有(Root 上):toast(必填的 Toast.Root.ToastObject:id、title、description、type、timeout、priority、updateKey、limited、height、onClose、onRemove、actionProps、positionerProps、data);swipeDirection(单值或数组)。
7.5 Title / Description / Action / Close
| 部件 | 映射 | 要点 |
|---|---|---|
Toast.Title |
Toast.Title |
仅 asChild → render。Base 渲染 <h2>(Radix 渲染 <div>),要保留 div 就 render={<div />}。内容通常来自 toast.title。 |
Toast.Description |
Toast.Description |
仅 asChild → render。Base 渲染 <p>。内容通常来自 toast.description。 |
Toast.Action |
Toast.Action |
asChild → render;altText(必填)删除,无对应 prop——经 manager 创建 toast 时,把按钮的 props(含 handler 与 aria 属性)通过 add({ actionProps }) 传入。Base 独有 nativeButton(boolean,默认 true,render 非按钮时设 false)。 |
Toast.Close |
Toast.Close |
asChild → render;同样有 nativeButton。 |
7.6 Base UI 独有 props、manager API、Data 属性与 CSS 变量
- Provider:
limit(number,默认3;超出的 toast 会被加data-limited+inert,而不是被移除)、toastManager(来自Toast.createToastManager(),供 React 外部使用)。 - Manager API(
useToastManager()返回值 /createToastManager()):toasts、add(options) => id、close(id?)、update(id, options)、promise(promise, { loading, success, error })。 - 新部件:
Toast.Content(堆栈折叠时裁剪溢出;data-behind、data-expanded)、Toast.Positioner/Toast.Arrow(锚定式 toast,完整 popup 定位面:anchor、side默认'top'、align、sideOffset、alignOffset、collisionAvoidance、collisionBoundary、collisionPadding、arrowPadding、sticky、positionMethod、disableAnchorTracking)。
Data 属性对照:
| Radix | Base UI |
|---|---|
Root [data-state="open" | "closed"] |
data-starting-style / data-ending-style(CSS 过渡钩子) |
Root [data-swipe="start" | "move" | "cancel" | "end"] |
滑动中为 [data-swiping];"end" ≈ [data-ending-style][data-swipe-direction=...] |
Root [data-swipe-direction](up/down/left/right) |
同名同值 |
| - | 新增:Root 的 data-expanded、data-limited、data-type;Viewport 的 data-expanded;Content 的 data-behind、data-expanded;Title/Description/Close/Action 的 data-type;Positioner/Arrow 的 data-side、data-align、data-anchor-hidden/data-uncentered |
CSS 变量对照:
| Radix | Base UI |
|---|---|
--radix-toast-swipe-move-x / --radix-toast-swipe-move-y |
--toast-swipe-movement-x / --toast-swipe-movement-y |
--radix-toast-swipe-end-x / --radix-toast-swipe-end-y |
删除;基于 [data-ending-style][data-swipe-direction=...] + movement 变量做消失动画 |
| - | Root 新增:--toast-index、--toast-offset-y、--toast-height;Viewport:--toast-frontmost-height;Positioner:--anchor-width/height、--available-width/height、--transform-origin |
一个来自 universal-patterns.md 的覆盖矩阵注记:Toast 不属于 registry 的「golden pair」组件对(shadcn 用户大多直接用 sonner),因此其规格完全依据 Base 文档编写——迁移时若项目中实际使用 sonner,按 SKILL.md 的硬规则应原样保留并在报告中列为「有意不迁移」。
八、form:Radix Form 拆成 Form / Field / Fieldset 三件套
Base UI 把 Radix Form 拆到三个组件:Form(@base-ui/react/form,可调用单部件,渲染 <form>)、Field(@base-ui/react/field:Root、Label、Control、Error、Description、Validity、Item)、Fieldset(@base-ui/react/fieldset:Root、Legend)。
8.1 部件映射
| Radix 部件 | Base UI 部件 |
|---|---|
Form.Root |
Form(可调用,无 .Root) |
Form.Field |
Field.Root |
Form.Label |
Field.Label |
Form.Control |
Field.Control(或任意 Base UI 输入组件:Input、Checkbox、Select 等开箱即用即可放入 Field) |
Form.Message |
Field.Error(校验错误);纯提示文字用 Field.Description |
Form.ValidityState |
Field.Validity |
Form.Submit |
删除;用普通 <button type="submit"> |
| - | 新增:Fieldset.Root + Fieldset.Legend、Field.Item(checkbox/radio 组中逐项包装) |
8.2 Form.Root → Form
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
onClearServerErrors |
() => void / - |
删除(有替代) | 服务端错误模型变化:向 Form 传入 errors 对象(key 为 Field.Root 的 name,value 为消息),并在 onFormSubmit(Base 会替你 preventDefault())或逐字段 onValueChange 里自行清除错误状态。 |
Base UI 独有(Form 上):errors(Errors)、onFormSubmit((formValues, eventDetails) => void)、validationMode('onSubmit' | 'onBlur' | 'onChange',默认 'onSubmit')、actionsRef({ validate(fieldName?) })。
8.3 Form.Field → Field.Root
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
name(必填) |
string / - |
name |
目的相同(提交标识 + 匹配 Form errors 的 key);Base UI 中为可选,且优先于 Field.Control 上的 name。 |
serverInvalid |
boolean / - |
删除(有替代) | 二选一:通过 Form 的 errors={{ [name]: message }} 提供消息(字段变为 invalid,Field.Error 展示它),或用 Field.Root 的 invalid 布尔 prop 强制状态。 |
Base UI 独有(Field.Root 上):validate((value, formValues) => string | string[] | Promise<...> | null,取代 Radix 函数式 match 的自定义校验)、validationMode、validationDebounceTime(0)、disabled、invalid、dirty、touched、actionsRef({ validate() })。
8.4 Label / Control / Message
| 映射 | 要点 |
|---|---|
Form.Label → Field.Label |
asChild → render;与控件的自动关联保留。Base 独有 nativeLabel(boolean,默认 true;render 换成非 label 元素时设 false,例如用 <div> 给 <Select.Trigger> 按钮打标签)。 |
Form.Control → Field.Control |
asChild → render。复合控件可以完全跳过 Control:Base UI 输入组件(Input、Checkbox、Select…)直接接入 Field.Root,这是 Radix Form 做不到的。Base 独有 defaultValue(string | number | string[])、onValueChange((value, eventDetails) => void)。 |
Form.Message → Field.Error |
见下表。 |
Form.Message 的 match 是本表最需要逐字段核对的一项:
| Radix prop | 类型 / 默认 | Base UI 对应 | 迁移说明 |
|---|---|---|---|
asChild |
boolean / false |
render |
签名变更。 |
match |
'badInput' | 'patternMismatch' | 'rangeOverflow' | 'rangeUnderflow' | 'stepMismatch' | 'tooLong' | 'tooShort' | 'typeMismatch' | 'valid' | 'valueMissing' | ((value, formData) => boolean | Promise<boolean>) / - |
match |
签名变更:Base UI 为 boolean | 'valid' | 'badInput' | 'customError' | 'patternMismatch' | 'rangeOverflow' | 'rangeUnderflow' | 'stepMismatch' | 'tooLong' | 'tooShort' | 'typeMismatch' | 'valueMissing'。函数形式消失:自定义规则移到 Field.Root 的 validate(返回错误字符串),无 match 的 Error 组件随即展示它们;'customError' 匹配 validate 失败。 |
forceMatch |
boolean / false |
match={true} |
改名/吸收:match 接受 true 时始终显示消息(对外部库与服务端错误的文档化挂点)。 |
name |
string / - |
删除 | Field.Error 不能从外部指向字段;必须嵌套在所属的 Field.Root 内。 |
补充说明:Radix 在 children 省略时会按 match 渲染默认英文消息;Base UI 渲染的是来自 validate/Form errors 的错误字符串,否则请提供 children。Field.Description(仅 className/style/render)是非错误提示文字的新家。Error 渲染 <div>,Description 渲染 <p>。
8.5 ValidityState、Submit 与新 Fieldset
| Form.ValidityState | 映射到 Field.Validity:children 为必填渲染函数,签名变为 (state: Field.Validity.State) => React.ReactNode,原生标志位于 state.validity.*(另有 state.errors、state.error、state.value、state.initialValue);Radix 的 name prop 删除,必须嵌套在 Field.Root 内。 |
| Form.Submit | 无对应部件;渲染普通 <button type="submit">(或样式化 Button 组件)。 |
| 新增 Fieldset | Fieldset.Root 渲染原生 <fieldset>(props:className/style/render;state { disabled }、data-disabled)。Fieldset.Legend 渲染 <div> 并自动关联为无障碍 legend。用于把相关字段归在一个标签下。 |
8.6 表单级 Base UI 独有能力、Data 属性与 CSS 变量
- 校验时机可配置(Form 或逐 Field 的
validationMode、validationDebounceTime); - Form 与 Field.Root 都有
actionsRef命令式validate(); Field.Item把组内单个 checkbox/radio 与其自身 label/description 分组(disabledprop);- 提交时焦点移至第一个无效字段,与 Radix 行为一致。
Data 属性对照:
| Radix(Field/Label/Control/Message) | Base UI(全部 Field 部件:Root、Item、Label、Control、Description、Error) |
|---|---|
[data-valid] |
[data-valid](相同) |
[data-invalid] |
[data-invalid](相同) |
| - | 新增:data-dirty、data-touched、data-filled、data-focused、data-disabled;Error 另得 data-starting-style/data-ending-style。 |
CSS 变量:两侧均无。
九、无 Base UI 对应物的 Radix 工具:Label / AspectRatio / VisuallyHidden / AccessibleIcon
文档最后一节列出四个「无对应物」原语及其推荐的纯 CSS/原生替代——这些与 SKILL.md 的硬规则(「No Base UI counterpart」清单)完全一致:
- Label(Radix
Label.Root:asChild、htmlFor):用原生<label htmlFor="...">;在Field.Root内则用Field.Label(自动建立关联,无需htmlFor)。Radix 唯一的行为增强(防止双击选中文字)一行 CSS 即可:select-none/user-select: none。 - AspectRatio(Radix
AspectRatio.Root:asChild、ratio默认1):用 CSSaspect-ratio属性——该 prop 本就映射到它。仓库现有实现 apps/v4/registry/new-york-v4/ui/aspect-ratio.tsx 只是一个透传 Radix 原语的空壳,印证了这一点:ratio={16 / 9}→aspect-video或aspect-[16/9](aspect-ratio: 16 / 9),媒体子元素加w-full与object-cover。 - VisuallyHidden(Radix
VisuallyHidden.Root:asChild):在<span>上用 Tailwind 的sr-only类(标准 clip-rect 模式)。注意其他文件里一些 Base UI popup 组件仍需要隐藏标题满足 a11y,<span className="sr-only">同样覆盖该场景。 - AccessibleIcon(Radix
AccessibleIcon.Root:label必填):它只是 VisuallyHidden +aria-hidden的组合:给图标渲染aria-hidden="true"(或focusable="false")并在旁边加<span className="sr-only">{label}</span>,或者干脆把aria-label={label}放到可交互的父级(button/link)上。
十、落到实处的迁移要点
把本文当参考词典后,执行层面遵循技能包既有纪律(均来自 SKILL.md):
- Preflight:先跑
npx shadcn@latest info --json确认当前 base/风格/Tailwind 版本,用项目自己的包管理器安装依赖,保持 git 工作区干净,每个组件一个 commit,并在动依赖前先跑基线 typecheck/build。 - @base-ui/react 与 radix 并存安装,只有最后一个 Radix 组件迁移完成后才移除 Radix 依赖。
- shadcn 风格的包装器优先走「golden pair」:整库模式下用
shadcn add <component> --overwrite直接交付 base 变体;渐进模式下把 base 变体写到<component>-base.tsx(绝不动原文件),消费者逐个改 import 后再删除旧文件。本文表格主要服务于「转换引擎」路径——手写 Radix 代码、非 shadcn 项目、legacy 风格(new-york/new-york-v4/default 没有 base-new-york 对应风格,只允许分类检测、只重连原语、保留用户自己的 class)。 - 行为差异只标记、不静默修补:toast 的 hotkey 从可配置 F8 变为硬编码 F6、Separator 默认语义化、Form 焦点行为等,都应在
.migration/<component>.md报告的「Behavior changes」段落中列出。 - 收尾自检:逐文件 typecheck、逐批 build、最后与基线对比全量 build;对本文涉及的每个文件跑残留扫描
grep -n "radix-ui\|@radix-ui",确认零命中后才算迁移完成。
按以上结构执行,progress/scroll-area/separator/avatar 属于低风险直映(改 import、改名、换 render),toast 需要重写状态管理模型,form 需要按三件套重组——六族的每一个 Prop 级决策,现在都可以在 skills/migrate-radix-to-base/display-misc.md 与本仓库 registry 源码之间找到对应依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00