Skip to content

[6.x] Fix Checkbox & Radio label click capturing and useId prop binding - #15139

Open
ibrokemycomputer wants to merge 1 commit into
statamic:6.xfrom
ibrokemycomputer:fix/checkbox-radio-label-overlay
Open

[6.x] Fix Checkbox & Radio label click capturing and useId prop binding#15139
ibrokemycomputer wants to merge 1 commit into
statamic:6.xfrom
ibrokemycomputer:fix/checkbox-radio-label-overlay

Conversation

@ibrokemycomputer

@ibrokemycomputer ibrokemycomputer commented Aug 8, 2026

Copy link
Copy Markdown

Summary

Fixes useId and id prop binding in Checkbox/Item.vue and Radio/Item.vue:

  1. useId & Prop Binding Flaw: Checkbox/Item.vue and Radio/Item.vue called useId() internally as a fixed local variable (const id = useId()) instead of exposing an id prop. Checkbox/Item.vue also imported useId from reka-ui rather than Vue. Passing a custom id prop was ignored, breaking accessibility customization, causing duplicate or mismatched ID target bindings across items, and failing to align with Statamic's standard UI component design pattern (id: { type: String, default: () => useId() }).
  2. Preserved Full-Item Click Target ([6.x] Make the whole radio/checkbox item clickable #14821): Restores proper ID matching between <label :for="props.id"> and <CheckboxRoot :id="props.id"> / <RadioGroupItem :id="props.id">, ensuring that the intended before:absolute before:inset-0 full-item click overlay (which makes descriptions and chip paddings clickable) targets the correct control without misfiring or triggering adjacent components.

Reproduction

  1. Render <CheckboxItem id="custom-id"> or <RadioItem id="custom-id">.
  2. Observed Behavior: The component ignored props.id and used an internal auto-generated ID. Because reka-ui's useId / missing prop binding generated mismatched or non-unique IDs across fieldsets, clicking a label or its full-item overlay triggered wrong input controls or ignored explicit ID overrides.
  3. Expected Behavior: The component accepts an explicit id prop, falling back to Vue's native useId() when omitted, and cleanly links <label :for> to the input element.

Minimal Reproduction Repository:
https://github.com/ibrokemycomputer/statamic-input-error-poc


Root Cause

const id = useId() was defined directly in <script setup> scope without exposing an id prop, preventing overrides. Additionally, Checkbox/Item.vue imported useId from reka-ui instead of 'vue', creating ID generation inconsistencies across Statamic's component library.


Solution

Standardized useId & Added id Prop:

  • Imported useId from 'vue' across both components.
  • Added id: { type: String, default: () => useId() } to defineProps.
  • Updated template bindings to use props.id for :id on <CheckboxRoot> / <RadioGroupItem>, :for on <label>, and :id on <p>/<span> description elements.
  • Retained before:absolute before:inset-0 before:content-[''] on <label> so the entire item box (including descriptions and chip padding) remains clickable as intended in PR [6.x] Make the whole radio/checkbox item clickable #14821.

Changes

  • resources/js/components/ui/Checkbox/Item.vue

    • Swapped reka-ui useId import for Vue's native useId.
    • Added id: { type: String, default: () => useId() } prop.
    • Updated :id, :for, and aria-describedby template bindings to use props.id.
  • resources/js/components/ui/Radio/Item.vue

    • Imported useId from 'vue'.
    • Added id: { type: String, default: () => useId() } prop.
    • Updated :id, :for, and aria-describedby template bindings to use props.id.

@jasonvarga jasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overlaps with #15172, which fixes a related but distinct bug worth folding in here.

Note

I'm happy to update this PR with appropriate fixes myself. I'm just putting this feedback here so this doesn't get merged without it. You're welcome to do it if you want, though.

The bug: Checkbox/Item.vue calls useId() with no way to override it. Vue's useId() generates ids from a counter scoped to the Vue app instance, not the whole page. Statamic mounts separate Vue app instances for things like widgets and repeated bard/replicator sets, so each one restarts its own id counter — meaning two unrelated checkboxes in two different bard sets/widgets can end up with the same generated id (e.g. both reka-v-52). Since that id backs both <CheckboxRoot :id> and <label :for>, a collision breaks the label↔input association: clicking one checkbox's label can toggle a totally different checkbox, or fail to toggle anything.

Why this matters for this PR: this PR exposes an id prop on Checkbox/Item.vue and Radio/Item.vue, but nothing here actually supplies an override — so the collision bug above isn't fixed by this PR alone, just made fixable. #15172 closes the loop for checkboxes by having CheckboxesFieldtype.vue pass a deterministic id (derived from the field's full path, e.g. field_page_content_2_..._card_rounded_corners_0) into that new prop, so it's unique per-field regardless of how many separate Vue app instances exist.

Since this PR already touches the same lines of Checkbox/Item.vue (and also fixes Radio/Item.vue, which has the identical problem), it'd be good to fold #15172's CheckboxesFieldtype.vue change in here to avoid a merge conflict between the two. And since RadioFieldtype.vue has the same v-for pattern as CheckboxesFieldtype.vue, it'd make sense to give it the same fix (a unique id per radio option) while this PR is already touching Radio/Item.vue.

@ibrokemycomputer
ibrokemycomputer force-pushed the fix/checkbox-radio-label-overlay branch from 41547f9 to 0c576bf Compare August 12, 2026 21:10
@ibrokemycomputer

ibrokemycomputer commented Aug 12, 2026

Copy link
Copy Markdown
Author

@jasonvarga Thank you for the feedback!

I added the changes from #15172 and combined them all in one commit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants