返回博客

Base UI CheckboxGroup 静默失效:是 value 在匹配,不是 name

引入 @base-ui-components/reactCheckboxGroup,照着直觉把每个子 Checkbox 的标识符写在 name 上,再把同一批字符串塞进 defaultValue——看起来无懈可击。跑起来之后,却发现所有复选框都没有选中态,点击也没有任何回调。DOM 检查一切正常:role="group" 在,三个 role="checkbox" 都在,渲染完全没问题。

这是一种最难排查的 bug——组件「装好了」,却什么都不动。

现象

Checkboxname="apple",给 CheckboxGroupdefaultValue={["apple"]}

// 看起来合理,实际完全不生效
<CheckboxGroup defaultValue={["apple"]} onValueChange={(v) => console.log(v)}>
  <Checkbox name="apple" />
  <Checkbox name="banana" />
</CheckboxGroup>
  • aria-checked 恒为 falsedefaultValue 不生效
  • 点击后 onValueChange 收到 undefined,或根本不触发
  • DOM 结构渲染完全正常,role="group" + 子 role="checkbox" 全部存在
  • 极易被误判为「组件已正常接入,是状态管理哪里出了问题」

根因

翻开 @base-ui-components/react/checkbox/root/CheckboxRoot.js 源码:

// line 126-127 —— 勾选态完全由 value 匹配,name 不参与
controlled: value && groupValue && !parent
  ? groupValue.includes(value)
  : groupChecked,
default:    value && defaultGroupValue && !parent
  ? defaultGroupValue.includes(value)
  : defaultChecked,
 
// line 200 —— 写回也用 value
if (value && groupValue && setGroupValue && !parent) { ... }

name 在源码里只出现在表单提交路径(line 178: name: parent ? undefined : name),与 group 协调完全无关

CheckboxGroup 的整个成员匹配机制——初始选中态、受控 value、onValueChange 写回——全部走 value prop,name 是透明的。

坑:name 和 value 在 Base UI 里职责不重叠

HTML 原生 checkbox 里,name 是表单分组键,功能上和「哪个选项被选中」高度相关,这个心智模型带进 Base UI 会踩坑。

Base UI 的 CheckboxGroup 是受控组合件,用 value 数组做成员匹配。name 仅在 <form> 提交时透传到底层 <input>,根本不参与 group 状态协调。

name 而不是 value 的后果:defaultValue/受控 value/onValueChange 全部静默失效,没有任何警告。

正解

将每个 Checkbox 的标识符从 name 改为 value,group 的 defaultValue/value 数组同步对应:

<CheckboxGroup defaultValue={["apple"]} onValueChange={(v) => console.log(v)}>
  <Checkbox value="apple" label="苹果" />
  <Checkbox value="banana" label="香蕉" />
</CheckboxGroup>

如果还需要表单提交,valuename 可以同时传,互不干扰:

<Checkbox value="apple" name="fruit" label="苹果" />
正解:成员标识符用 value,name 留给表单

value 驱动 group 的勾选态和回调;name 驱动原生表单提交字段名。两者职责独立,同时传没有冲突。

测试坑(jsdom 下交互验证失效)

分组 Checkbox 的切换走 Base UI 内部的 pointer 事件,jsdom 环境下以下三种写法均不触发 onValueChange

  • fireEvent.click(span[role=checkbox])
  • 直接点击隐藏的 <input>
  • 点击外层 <label>

实测全部静默。

单测只能断言静态态:通过 defaultValue/受控 value 验证 aria-checked,验证多选共存、disabled 向下传递、role 计数。交互正确性需要 Playwright 或截图测试兜底,不要在 jsdom 里死磕点击回调。

一句话外卖

Base UI CheckboxGroupvalue 匹配成员,name 只管表单提交——把 HTML 原生 checkbox 的 name 心智模型带进来,会踩一个完全静默的坑。