Repository navigation
fix(bulma-ui): associate the form label prop with its control via a generated id - #492
Conversation
…enerated id The convenience label prop rendered a sibling <label> through Field with no htmlFor/id wiring, so clicking the label did nothing and assistive tech got no name. Input, Select, TextArea, File, Numberinput, Slider, DateInput, TimeInput, and DateTimeInput now generate an id with useId and wire htmlFor when label is set: a user-supplied id is used as the target instead, an explicit labelProps.htmlFor wins outright, and no id is injected without a label (or in modes that render no labellable control: inside an outer Field, plusminus bare, date-family inline). Group and composite inputs (Checkboxes, Radios, Autocomplete, Rate, Taginput) and Field-composed usage still need manual wiring; follow-up issues cover those. Docs accessibility sections, the bestax-form skill, its Storybook showcase, and the forms blog note are updated to match.
|
Warning Review limit reached
Next review available in: 11 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
WalkthroughConvenience form controls now use ChangesAutomatic label association
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant FormControl
participant useAutoLabelId
participant Field
participant ControlBase
FormControl->>useAutoLabelId: provide label, id, labelProps, and context
useAutoLabelId-->>FormControl: return controlId and fieldLabelProps
FormControl->>Field: pass resolved fieldLabelProps
FormControl->>ControlBase: pass resolved controlId
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Closes #368 by automatically associating convenience form labels with their controls.
Changes:
- Adds an internal
useId-based label association hook across nine controls. - Adds coverage for generated IDs, overrides, nested fields, and special modes.
- Updates documentation, skill guidance, and examples.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
skills/bestax-form/SKILL.md |
Updates form-label guidance and example. |
skills/bestax-form/references/api.md |
Clarifies composed-field labeling. |
docs/docs/api/form/textarea.md |
Documents TextArea association. |
docs/docs/api/form/slider.md |
Documents Slider association. |
docs/docs/api/form/select.md |
Documents Select association. |
docs/docs/api/form/numberinput.md |
Documents Numberinput association. |
docs/docs/api/form/input.md |
Documents Input association. |
docs/docs/api/form/file.md |
Documents File’s dual labels. |
docs/docs/api/form/field.md |
Clarifies manual Field wiring. |
docs/docs/api/form/datetime/timeinput.md |
Documents TimeInput behavior. |
docs/docs/api/form/datetime/datetimeinput.md |
Documents DateTimeInput behavior. |
docs/docs/api/form/datetime/dateinput.md |
Documents DateInput behavior. |
docs/blog/2026-08-04-forms-without-a-form-library.md |
Updates historical accessibility note. |
bulma-ui/src/skill-examples/Form.stories.tsx |
Removes obsolete manual wiring. |
bulma-ui/src/form/useAutoLabelId.ts |
Implements shared ID association. |
bulma-ui/src/form/TimeInput.tsx |
Wires TimeInput labels. |
bulma-ui/src/form/TextArea.tsx |
Wires TextArea labels. |
bulma-ui/src/form/Slider.tsx |
Wires Slider labels. |
bulma-ui/src/form/Select.tsx |
Wires Select labels. |
bulma-ui/src/form/Numberinput.tsx |
Wires Numberinput labels. |
bulma-ui/src/form/Input.tsx |
Wires Input labels. |
bulma-ui/src/form/File.tsx |
Wires File labels. |
bulma-ui/src/form/fieldProps.ts |
Documents neutral shared props. |
bulma-ui/src/form/Field.tsx |
Clarifies Field’s manual association. |
bulma-ui/src/form/DateTimeInput.tsx |
Wires DateTimeInput labels. |
bulma-ui/src/form/DateInput.tsx |
Wires DateInput labels. |
bulma-ui/src/form/__tests__/TimeInput.test.tsx |
Tests TimeInput association. |
bulma-ui/src/form/__tests__/TextArea.test.tsx |
Tests TextArea association. |
bulma-ui/src/form/__tests__/Slider.test.tsx |
Tests Slider association. |
bulma-ui/src/form/__tests__/Select.test.tsx |
Tests Select association. |
bulma-ui/src/form/__tests__/Numberinput.test.tsx |
Tests Numberinput modes. |
bulma-ui/src/form/__tests__/Input.test.tsx |
Tests association and overrides. |
bulma-ui/src/form/__tests__/File.test.tsx |
Tests File’s dual labels. |
bulma-ui/src/form/__tests__/DateTimeInput.test.tsx |
Tests DateTimeInput association. |
bulma-ui/src/form/__tests__/DateInput.test.tsx |
Tests IDs and popover derivation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const controlId = | ||
| id ?? (active && !labelProps?.htmlFor ? generatedId : undefined); |
Preview DeploymentPreview URL: https://502ae397.bestax.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bulma-ui/src/form/Input.tsx`:
- Around line 17-21: Update the documentation for the label and
labelProps-related props in Input to clarify that automatic label association
applies only when Input renders its own Field; when nested in an outer Field,
callers must manually associate that Field’s label because Input does not render
or forward the label props.
In `@docs/docs/api/form/datetime/dateinput.md`:
- Line 832: Update the label descriptions in
docs/docs/api/form/datetime/dateinput.md:832-832,
docs/docs/api/form/datetime/datetimeinput.md:836-836, and
docs/docs/api/form/datetime/timeinput.md:971-971 to replace “renders no input”
with wording that states inline mode has no visible input to label.
In `@docs/docs/api/form/field.md`:
- Line 492: Document the complete label-association contract across all cited
sites: in docs/docs/api/form/field.md lines 492-492 and 517-531, limit automatic
association and the label prop behavior to convenience controls rendered outside
an existing Field; in docs/docs/api/form/file.md lines 407-407 and 431-454,
qualify the two-label behavior, add matching id/htmlFor values to manual
examples, and state that explicit labelProps.htmlFor suppresses fallback ID
generation; apply matching labelProps.htmlFor/id examples and explicit-override
conditions in docs/docs/api/form/input.md lines 569-570 and 593-624,
docs/docs/api/form/numberinput.md lines 305-334, and
docs/docs/api/form/select.md lines 410-411 and 434-464; in
docs/docs/api/form/slider.md lines 344-379, document standalone rendering,
explicit overrides, and manual association when wrapped in Field.
In `@skills/bestax-form/SKILL.md`:
- Around line 122-127: Update the automatic label-association guidance in
skills/bestax-form/SKILL.md lines 122-127 to state that inline date/time picker
modes have no visible input and do not support the generated id/htmlFor
association; apply the same exception to the Field automatic-wiring note in
skills/bestax-form/references/api.md line 19. Keep the existing component and
manual-wiring guidance intact.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dc44b6f1-a813-49b1-bf71-a277a4f4e1ef
📒 Files selected for processing (35)
bulma-ui/src/form/DateInput.tsxbulma-ui/src/form/DateTimeInput.tsxbulma-ui/src/form/Field.tsxbulma-ui/src/form/File.tsxbulma-ui/src/form/Input.tsxbulma-ui/src/form/Numberinput.tsxbulma-ui/src/form/Select.tsxbulma-ui/src/form/Slider.tsxbulma-ui/src/form/TextArea.tsxbulma-ui/src/form/TimeInput.tsxbulma-ui/src/form/__tests__/DateInput.test.tsxbulma-ui/src/form/__tests__/DateTimeInput.test.tsxbulma-ui/src/form/__tests__/File.test.tsxbulma-ui/src/form/__tests__/Input.test.tsxbulma-ui/src/form/__tests__/Numberinput.test.tsxbulma-ui/src/form/__tests__/Select.test.tsxbulma-ui/src/form/__tests__/Slider.test.tsxbulma-ui/src/form/__tests__/TextArea.test.tsxbulma-ui/src/form/__tests__/TimeInput.test.tsxbulma-ui/src/form/fieldProps.tsbulma-ui/src/form/useAutoLabelId.tsbulma-ui/src/skill-examples/Form.stories.tsxdocs/blog/2026-08-04-forms-without-a-form-library.mddocs/docs/api/form/datetime/dateinput.mddocs/docs/api/form/datetime/datetimeinput.mddocs/docs/api/form/datetime/timeinput.mddocs/docs/api/form/field.mddocs/docs/api/form/file.mddocs/docs/api/form/input.mddocs/docs/api/form/numberinput.mddocs/docs/api/form/select.mddocs/docs/api/form/slider.mddocs/docs/api/form/textarea.mdskills/bestax-form/SKILL.mdskills/bestax-form/references/api.md
💤 Files with no reviewable changes (1)
- bulma-ui/src/skill-examples/Form.stories.tsx
| /** Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. */ | ||
| label?: React.ReactNode; | ||
| /** Size for the label. */ | ||
| labelSize?: FieldProps['labelSize']; | ||
| /** Props for the label element. */ | ||
| /** Props for the label element. An explicit `htmlFor` here overrides the automatic association. */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Qualify the automatic-association documentation for nested Field usage.
When Input is inside a Field, rendersLabel is false. Input does not render its own label or forward labelProps to the outer Field. Lines 17 and 21 describe association without this condition.
State that automatic association applies when Input renders its own Field. State that callers must manually associate an outer Field label.
Proposed documentation fix
-/** Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. */
+/** Field label. When Input renders its own Field, it is automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. */
...
-/** Props for the label element. An explicit `htmlFor` here overrides the automatic association. */
+/** Props for Input's rendered label. An explicit `htmlFor` here overrides the automatic association. */📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Field label. Automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. */ | |
| label?: React.ReactNode; | |
| /** Size for the label. */ | |
| labelSize?: FieldProps['labelSize']; | |
| /** Props for the label element. */ | |
| /** Props for the label element. An explicit `htmlFor` here overrides the automatic association. */ | |
| /** Field label. When Input renders its own Field, it is automatically associated with the input via `htmlFor` — uses your `id` when provided, otherwise a generated one. */ | |
| label?: React.ReactNode; | |
| /** Size for the label. */ | |
| labelSize?: FieldProps['labelSize']; | |
| /** Props for Input's rendered label. An explicit `htmlFor` here overrides the automatic association. */ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bulma-ui/src/form/Input.tsx` around lines 17 - 21, Update the documentation
for the label and labelProps-related props in Input to clarify that automatic
label association applies only when Input renders its own Field; when nested in
an outer Field, callers must manually associate that Field’s label because Input
does not render or forward the label props.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.
Suppressed comments (2)
bulma-ui/src/form/useAutoLabelId.ts:40
- An explicit empty
htmlFordoes not suppress generation as documented: the truthiness check generates a control ID, while the later spread keeps the label'sfor="". This causes unexpected DOM churn and leaves the generated ID unassociated. Check whetherhtmlForis defined rather than whether its value is truthy.
const controlId =
id ?? (active && !labelProps?.htmlFor ? generatedId : undefined);
skills/bestax-form/SKILL.md:219
- The checklist recommends
htmlForplus an ID for group inputs, but a<label for>can label only one labelable control; it cannot name checkbox/radio/rating groups. This would preserve the accessibility defect for generated forms. Group containers needrole="group"/role="radiogroup"andaria-labelledby, while composite controls need their inner control labeled.
There was a problem hiding this comment.
Deep review — 0 blocking · 2 advisory
| No. | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🔵 Advisory | Accessibility | File now emits two labels on one input (Field label-for plus the wrapping file-label); accessible name concatenates and axe flags form-field-multiple-labels (moderate). Net-positive vs. #368 and documented, on the record. |
bulma-ui/src/form/File.tsx:150 |
| 2 | 🔵 Advisory | API | Nine label-bearing controls now emit id/for where they emitted none, shipped as a fix (patch). Additive but churns consumer snapshots and changes the DateInput-family popover id shape (<id>-popover vs picker-<reactId>) when a label is present. |
bulma-ui/src/form/useAutoLabelId.ts:39 |
Overall: The change is sound and does exactly what #368 asks — one small, well-scoped useId-based hook that merges htmlFor into the label props each convenience component already forwards, with the control receiving a matching id. I verified empirically: getByLabelText resolves, for equals the control id, and all 572 form tests pass on the checked-out commit. The hook is called unconditionally (hooks-safe) and useId is SSR-stable on both React majors. The riskiest surface is the File double-label tradeoff and the DateInput family id-to-popover-id derivation; both are handled and covered by tests. A human should focus on confirming the patch-vs-minor call for the new id/for output (advisory 2) is acceptable given the documented consumer-snapshot churn.
Residual risk: the addressed failure class (label wired to no control) could recur three ways, each checked:
- Dangling
htmlFor(label points to a non-existent id): refuted — every path whererendersLabelis true renders a control that receivescontrolId(Input/Select/TextArea/File/Slider always; Numberinput only when isStepper or not-effectiveBare, matching the input-bearing branches; date family only when not-inline). WhenlabelProps.htmlForis user-set, generation is suppressed and the user owns the id. - Rest-spread clobbering
controlId: refuted — the spread carriesidonly when the caller supplied one, in which case controlId equals the user id, so the override is value-identical; with no user id the spread has noidkey andcontrolIdstands. - Still-unassociated labels elsewhere: acknowledged and out of scope — Autocomplete/Taginput (id lands on a wrapper), Rate/Checkboxes/Radios (need
aria-labelledby), inline date pickers (no input to target), and composed Field-label usage remain manual; all are called out in the PR body and docs, not silently dropped.
🏄 Clean little set wave, dude — one tidy hook paddles nine inputs into the a11y lineup and every test barrel rolls green. The File double-label is a mellow ripple, not a wipeout. Ship it.
Review round on #492: the auto-association docs stated the rule without its boundary conditions. TSDoc, the Field/File docs pages, and the bestax-form skill now say the wiring happens only when the component renders its own Field (nested inside one, the label prop is dropped), that inline picker modes have no visible input to label, and that an explicit labelProps.htmlFor suppresses id generation.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.
Suppressed comments (1)
bulma-ui/src/form/useAutoLabelId.ts:42
- When
labelPropscontainshtmlFor: undefined(for example, from a conditional value),controlIdis generated because the value is falsy, but the subsequent spread overwrites the matchinghtmlForwithundefined. The rendered control then has an ID while its label remains unassociated. Determine whether a defined override exists and writehtmlForafter spreading the remaining props.
const controlId =
id ?? (active && !labelProps?.htmlFor ? generatedId : undefined);
const fieldLabelProps = active
? { htmlFor: controlId, ...labelProps }
Preview DeploymentPreview URL: https://bbe312ea.bestax.pages.dev |
Three high advisories surfaced in the audit gate, none introduced by this branch: - nanoid <3.3.17 (GHSA-2v37-7h3g-55p8, vite>postcss chain): real patch available, forced via a 3.x-scoped override; 3.3.17 is past the release cooldown and postcss's ^3.3.16 admits it. - image-size <=2.0.2 ICNS and JXL/HEIF infinite-loop DoS (GHSA-w3rx-r6r6-pgpr, GHSA-5p2g-fcmc-qvqq, Docusaurus mdx-loader): GitHub lists no patched release and npm's latest is still 2.0.2, so there is nothing to force. Ignored via auditConfig.ignoreGhsas with removal criteria documented in pnpm-workspace.yaml; exposure is the docs build sizing this repo's own images, never untrusted input. pnpm audit --audit-level=high is clean again.
Preview DeploymentPreview URL: https://9e803e61.bestax.pages.dev |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 37 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (12)
bulma-ui/src/form/useAutoLabelId.ts:43
htmlForis checked by truthiness but then spread after the generated value. Consequently,htmlFor=""does not suppress ID generation as documented, whilehtmlFor={undefined}can erase the generated association. Distinguish an absent value from an explicit string and apply the resolved value last.
const controlId =
id ?? (active && !labelProps?.htmlFor ? generatedId : undefined);
const fieldLabelProps = active
? { htmlFor: controlId, ...labelProps }
: labelProps;
bulma-ui/src/form/Input.tsx:119
- The forwarded props are spread after
controlId, so a common<Input id={optionalId} label="…" />call whereoptionalIdisundefinedremoves the generated ID while the label retains its generatedhtmlFor. Apply the resolved ID after the forwarded props.
let content = <InputBase ref={ref} id={controlId} {...inputProps} />;
bulma-ui/src/form/Select.tsx:103
- The forwarded props are spread after
controlId, so an explicitly undefined optionalidremoves the generated ID while the label retains its generatedhtmlFor. Apply the resolved ID after the forwarded props.
let content = <SelectBase ref={ref} id={controlId} {...selectProps} />;
bulma-ui/src/form/TextArea.tsx:90
- The forwarded props are spread after
controlId, so an explicitly undefined optionalidremoves the generated ID while the label retains its generatedhtmlFor. Apply the resolved ID after the forwarded props.
let content = <TextAreaBase ref={ref} id={controlId} {...textAreaProps} />;
bulma-ui/src/form/TimeInput.tsx:115
- Because
basePropsis spread aftercontrolId, passing an optionalidwhose value isundefinedoverwrites the generated ID and leaves the label pointing at a missing target. SpreadbasePropsfirst and apply the resolved ID last.
id={controlId}
{...baseProps}
bulma-ui/src/form/DateInput.tsx:119
- Because
basePropsis spread aftercontrolId, passing an optionalidwhose value isundefinedoverwrites the generated ID and leaves the label pointing at a missing target. SpreadbasePropsfirst and apply the resolved ID last.
id={controlId}
{...baseProps}
bulma-ui/src/form/DateTimeInput.tsx:117
- Because
basePropsis spread aftercontrolId, passing an optionalidwhose value isundefinedoverwrites the generated ID and leaves the label pointing at a missing target. SpreadbasePropsfirst and apply the resolved ID last.
id={controlId}
{...baseProps}
bulma-ui/src/form/File.tsx:159
- The native props are spread after
controlId, so an explicitly undefined optionalidremoves the generated ID while the Field label keeps its generatedhtmlFor. Apply the resolved ID afterrest.
id={controlId}
{...rest}
bulma-ui/src/form/Numberinput.tsx:343
- The native props are spread after
controlId, so an explicitly undefined optionalidremoves the generated ID while the Field label keeps its generatedhtmlFor. Apply the resolved ID afterrest.
id={controlId}
{...rest}
bulma-ui/src/form/Slider.tsx:680
- The native props are spread after
controlId, so an explicitly undefined optionalidremoves the generated ID while the Field label keeps its generatedhtmlFor. Apply the resolved ID afterrest.
id={controlId}
{...rest}
bulma-ui/src/form/Slider.tsx:762
- In range mode,
restlikewise overwrites the low thumb's generated ID when the caller passesid={undefined}, leaving the Field label'shtmlForunresolved. ApplycontrolIdafter the spread.
id={controlId}
{...rest}
skills/bestax-form/SKILL.md:222
- The checklist groups composite controls with labelable inputs and recommends
htmlForplus an ID, butCheckboxes,Radios, andRaterender group containers rather than labelable elements. A<label for>targeting those containers does not name the group; the PR description correctly calls for a group role plusaria-labelledby.
|
🎉 This PR is included in version 5.8.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 4.0.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 2.0.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Closes #368.
What
The convenience
labelprop on the form inputs rendered a sibling<label class="label">throughFieldwith nohtmlFor/idassociation: clicking the label didn't focus the control, assistive tech announced the control with no name, andgetByLabelTextfailed. This wires it automatically for the single-control components: Input, Select, TextArea, File, Numberinput, Slider, DateInput, TimeInput, DateTimeInput.How
One internal hook,
bulma-ui/src/form/useAutoLabelId.ts(not exported, not public API), called by each convenience component.Fielditself is unchanged; the hook mergeshtmlForinto thelabelPropsthe component already forwards.Rules:
label→ nothing changes (no id injected, zero DOM churn).labelset → the control gets auseId-generated id and the label gets a matchingfor.idis used as the target instead of a generated one.labelProps.htmlForalways wins, and suppresses id generation.Field(label is dropped, as before), Numberinput plusminusbare, and the date family'sinlinemode.Edge decisions:
file-labelCTA); accessible name is the concatenation and clicking the Field label opens the picker. axe's best-practice ruleform-field-multiple-labelsmay flag this as moderate; noted in the docs.aria-label.aria-controls="<id>-popover").Consumer-visible change
<Input label>(and the other eight) now emitsid/forattributes where it previously emitted none. Consumer snapshot tests will churn (the generated value is render-order dependent and differs between React 18:r0:and React 19«r1»).Out of scope (follow-up issues)
idcurrently lands on a wrapper div, needs threading to the inner input.role+aria-labelledby, nothtmlFor.<Field label>composed usage stays manual (labelProps={{ htmlFor }}+ matchingid), as documented.Docs / skill
TSDoc updated on all nine (drives the generated props tables;
FormFieldPropsdeliberately left neutral since group inputs also inherit it), hand-written Accessibility sections rewritten (input/select/textarea/field/file),skills/bestax-formcaveat + checklist + signup example updated with the convenience/composed distinction kept precise, the skill-examples Storybook showcase drops the workaround, and the forms blog post note is reworded.Testing
~30 new association tests across the nine components (association, user id, htmlFor override, no-label/no-id, inside-Field, horizontal, bare, range, inline, popover-id derivation), all React 18/19-safe (no id text asserted).
pnpm allgreen; touched files at 100% coverage.Summary by CodeRabbit
New Features
labelProps.htmlForoverrides remain supported.Documentation
Tests