Skip to content

[fields] a boolean field's visible form label is associated with nothing — BooleanField overrides the control id with the field name, so the form's label for dangles #3952

Closed

Description

@yinlianghui

Found while browser-verifying #3910 (docs field examples moved to form hosting). Out of that card's scope — pre-existing and independent of the hosting change, so filed rather than fixed.

What happens

Render any boolean field inside a form ({ type: 'form', fields: [{ name: 'notifications', label: 'Email Notifications', type: 'boolean' }] }) and the form emits two labels carrying the same text, of which the visible one points at an element that does not exist:

labels = [
  { for: "_r_3_-form-item", text: "Email Notifications", srOnly: false },   <-- visible
  { for: "notifications",   text: "Email Notifications", srOnly: true  }    <-- widget's own
]
switch id       = "notifications"   role=switch
all ids in form = ["notifications"]

label for="_r_3_-form-item" -> target exists? false
label for="notifications"   -> target exists? true

Cause

packages/components/src/renderers/form/form.tsx renders its FormLabel with the for that FormItem/FormControl generate (the _r_N_-form-item id). But packages/fields/src/widgets/BooleanField.tsx:16-17 replaces the control's id with the field name:

const generatedId = useId();
const id = config?.name || generatedId;

and then puts that id on the Switch/Checkbox (:30, :44) with its own sr-only label pointing at it (:35, :49). So the id the form told the control to use is discarded, the form's visible label references a now-nonexistent id, and the widget supplies a second, duplicate label.

Impact

  • Clicking the visible label does not toggle the switch — the normal affordance for a checkbox/switch row is dead. This is every boolean/checkbox field on every generated form in every app, not a docs-only artifact.
  • The accessible name survives only because the widget's sr-only label happens to be correctly wired; the form's own label contributes nothing. Any future change that drops the widget's internal label would silently leave the switch unnamed.
  • Duplicate label text in the DOM is also why a naive getByText(label) on a boolean field returns two nodes.

Only the widgets that override the id are affected. Text/number/etc. leave FormControl's id alone and their visible label works.

Suggested direction (not decided here)

The host should own the control id: BooleanField should use the id handed to it (the id prop the form's FormControl supplies) and fall back to useId()/name only when standalone — and then its internal sr-only label is redundant inside a form and should not be emitted twice. Worth checking the other widgets for the same id override while in there.

Reproduce

render(<SchemaRenderer schema={{ type: 'form', showSubmit: false, showCancel: false,
  fields: [{ name: 'notifications', label: 'Email Notifications', type: 'boolean' }] }} />);
// then compare every label's `for` against the ids present in the form

Confirmed in jsdom and in a real browser on /docs/fields/boolean (all three demos).

Activity

  1. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    CollaboratorAuthor

    PM 分诊(session_01GTRjn8xBqp75dk7kFupVRt):晋级 pm:queue —— 明确 bug 且方向清晰(host 拥有控件 id,BooleanField 应使用 FormControl 下发的 id,独立渲染时才回退 useId()/name),影响所有生成表单的 boolean/checkbox 字段(可见 label 点击失效),jsdom 与真实浏览器双重实证在册。修复时按正文建议顺带排查其它覆写 id 的 widget。


    Generated by Claude Code

  2. self-assigned this
    on Aug 9, 2026
  3. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    CollaboratorAuthor

    🔒 认领(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt,恢复批次 4)


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions