From a02ad8567e8607965652569b3c0ffffea4de674c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 19:33:00 +0000 Subject: [PATCH] fix(fields,components): name composite field groups by IDREF instead of an inert `for` (#3961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six widgets rendered a visible group label that was the accessible name of NOTHING. Two shapes, one outcome: `address` / `geolocation` replaced the host's id with their own sub-input ids (#3343), so the label's `for` named an id no element carried; `checkboxes` / `radio` / `rating` / `file` kept the id but on a `div`, where `label for` is inert HTML (`HTMLLabelElement.control` is null — it activates nothing and contributes no name). The WAI-ARIA group pattern, driven by a declaration rather than by the host guessing at widget DOM: - core: `ComponentMeta.labelling?: 'control' | 'group'`. Optional and additive; absent means `'control'`. - components: the form renderer branches on it. A `'group'` field's `FormLabel` publishes an `id` and drops its `for`; the widget receives `aria-labelledby`. The single-control path emits not one changed attribute (conditional spreads), so no field gains a second naming channel. `ui/form.tsx` is untouched — `FormLabel` spreads props after its own `htmlFor`, so both halves travel as ordinary props. - fields: the six audited widgets declare `labelling: 'group'`. address / geolocation move only the host id to the group container; checkboxes / rating answer with `role="group"`; radio keeps Radix's more specific `radiogroup`; file takes the name on its dropzone with no invented group layer (one control, merely not a labelable element). No new key in the widget props contract: `aria-*` is already declared on it and forwarded by `toDomProps`, the channel `aria-required` (#3290) uses. Unchanged on purpose: sub-labels keep naming their own inputs (an `aria-labelledby` on the first sub-input would OVERRIDE its own label), `aria-describedby` stays on the first focusable sub-input (#3318), the sub-input ids of #3343 do not move, and standalone rendering emits no role and no IDREF. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- .../composite-group-label-association-3961.md | 32 ++ .../form-group-label-association.test.tsx | 253 +++++++++++ .../components/src/renderers/form/form.tsx | 80 +++- packages/core/src/registry/Registry.ts | 24 ++ .../composite-group-label-e2e.test.tsx | 392 ++++++++++++++++++ .../group-labelling-declaration.test.ts | 100 +++++ packages/fields/src/index.tsx | 39 ++ packages/fields/src/widgets/AddressField.tsx | 34 +- .../fields/src/widgets/CheckboxesField.tsx | 9 + packages/fields/src/widgets/FileField.tsx | 12 +- .../fields/src/widgets/GeolocationField.tsx | 23 +- packages/fields/src/widgets/RadioField.tsx | 10 +- packages/fields/src/widgets/RatingField.tsx | 11 +- 13 files changed, 1011 insertions(+), 8 deletions(-) create mode 100644 .changeset/composite-group-label-association-3961.md create mode 100644 packages/components/src/renderers/form/__tests__/form-group-label-association.test.tsx create mode 100644 packages/fields/src/__tests__/composite-group-label-e2e.test.tsx create mode 100644 packages/fields/src/__tests__/group-labelling-declaration.test.ts diff --git a/.changeset/composite-group-label-association-3961.md b/.changeset/composite-group-label-association-3961.md new file mode 100644 index 0000000000..ec4a38c110 --- /dev/null +++ b/.changeset/composite-group-label-association-3961.md @@ -0,0 +1,32 @@ +--- +'@object-ui/core': minor +'@object-ui/components': patch +'@object-ui/fields': patch +--- + +Give composite and grouped field widgets a real accessible name: the form renderer now associates its label by IDREF for widgets that declare `labelling: 'group'`, instead of emitting a `