Skip to content

[field][docs] Missing usage of Field.Item for labelling radio and checkbox groups #3807

@imadroshan

Description

@imadroshan

Bug report

Current behavior

When composing CheckboxGroup with Field.Root and Fieldset.Root (using render={<CheckboxGroup ... />} as recommended by docs), the hidden <input type="checkbox"> elements created by Checkbox.Root end up with the same id across multiple checkboxes.

This also leads to label[for] collisions when using Field.Label (same for and same label id reused across multiple items).

This violates HTML id uniqueness rules and breaks label → control associations (and can confuse assistive tech and testing tools).

Reproducible example

import * as React from "react";
import { Field } from "@base-ui/react/field";
import { Fieldset } from "@base-ui/react/fieldset";
import { CheckboxGroup } from "@base-ui/react/checkbox-group";
import { Checkbox } from "@base-ui/react/checkbox";

export default function Repro() {
  return (
    <Field.Root name="allowedNetworkProtocols">
      <Fieldset.Root render={<CheckboxGroup defaultValue={["https"]} />}>
        <Fieldset.Legend>Allowed network protocols</Fieldset.Legend>

        {/* Version A: native label */}
        <label>
          <Checkbox.Root value="http" />
          HTTP
        </label>

        <label>
          <Checkbox.Root value="https" />
          HTTPS
        </label>

        <label>
          <Checkbox.Root value="ssh" />
          SSH
        </label>

        {/* Version B: Field.Label (also shows duplicate ids/for) */}
        {/*
        <Field.Label>
          <Checkbox.Root value="http" />
          HTTP
        </Field.Label>
        <Field.Label>
          <Checkbox.Root value="https" />
          HTTPS
        </Field.Label>
        <Field.Label>
          <Checkbox.Root value="ssh" />
          SSH
        </Field.Label>
        */}
      </Fieldset.Root>
    </Field.Root>
  );
}

Actual Results

Inspecting the DOM shows:

Version A (native )

All hidden inputs share the same id, e.g.:

<input id="base-ui-_r_1or_" ... name="allowedNetworkProtocols" />
<input id="base-ui-_r_1or_" ... name="allowedNetworkProtocols" />
<input id="base-ui-_r_1or_" ... name="allowedNetworkProtocols" />

(IDs differ per run, but within the same render they are duplicated.)

Version B (<Field.Label>)

Multiple labels share the same for and the same label element id is reused:

<label for="base-ui-_r_1s7_" id="base-ui-_r_1sh_">...</label>
<label for="base-ui-_r_1s7_" id="base-ui-_r_1sh_">...</label>
<label for="base-ui-_r_1s7_" id="base-ui-_r_1sh_">...</label>

<input id="base-ui-_r_1s7_" ... />
<input id="base-ui-_r_1s7_" ... />
<input id="base-ui-_r_1s7_" ... />

So multiple checkboxes end up referencing the same id.

Expected result

Each checkbox’s hidden <input> should have a unique id, and each label should reference the correct control.

At minimum:
no duplicate id values in the subtree
Field.Label should not generate duplicated for/id across sibling items

Why this matters

  • HTML validity: duplicate IDs are invalid.
  • Accessibility: label associations can be incorrect/ambiguous.
  • Testing: getByLabelText, Playwright label selectors, etc. can become flaky.
  • Potential UX: clicking a label may toggle the wrong checkbox depending on how associations resolve.

Base UI version

v1.1.0

Which browser are you using?

Chrome

Which OS are you using?

Windows

Additional context

Environment

  • react: 19.2.0
  • react-dom: 19.2.0
  • @base-ui/react: 1.1.0 (please fill exact)

Notes / suspicion

This looks like an ID generation / context bug where the Field.Root name="..." + Fieldset.Root composition causes the same generated ID to be reused for multiple checkbox inputs, rather than generating per-checkbox IDs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: fieldChanges related to the field component.docsImprovements or additions to the documentation.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions