From 1a57f711b91323c0909641bd91f46f60f7b215dd Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:15:32 +0100 Subject: [PATCH] fix(web-components): isolate radio option label targets --- .../test/basic/ic-radio-group.spec.ts | 36 +++++++++++++++++++ .../ic-radio-option/ic-radio-option.tsx | 20 +++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/web-components/src/components/ic-radio-group/test/basic/ic-radio-group.spec.ts b/packages/web-components/src/components/ic-radio-group/test/basic/ic-radio-group.spec.ts index 0465ed17f21..d56a4a18237 100644 --- a/packages/web-components/src/components/ic-radio-group/test/basic/ic-radio-group.spec.ts +++ b/packages/web-components/src/components/ic-radio-group/test/basic/ic-radio-group.spec.ts @@ -552,3 +552,39 @@ describe("ic-radio-group", () => { expect(fieldset?.hasAttribute("aria-label")).toBe(false); }); }); + +describe("ic-radio-group option ids", () => { + it("keeps label targets unique across repeated radio groups", async () => { + const page = await newSpecPage({ + components: [RadioGroup, RadioOption], + html: `
+ + + + + + + + +
`, + }); + + await page.waitForChanges(); + + const radioOptions = Array.from( + page.doc.querySelectorAll("ic-radio-option") + ); + const inputs = radioOptions.map((option) => + option.querySelector('input[type="radio"]') + ); + const labels = radioOptions.map((option) => + option.querySelector("label") + ); + const ids = inputs.map((input) => input?.id); + + expect(new Set(ids).size).toBe(ids.length); + labels.forEach((label, index) => { + expect(label?.getAttribute("htmlfor")).toBe(inputs[index]?.id); + }); + }); +}); diff --git a/packages/web-components/src/components/ic-radio-option/ic-radio-option.tsx b/packages/web-components/src/components/ic-radio-option/ic-radio-option.tsx index 54a332160d7..063e89ab9ff 100644 --- a/packages/web-components/src/components/ic-radio-option/ic-radio-option.tsx +++ b/packages/web-components/src/components/ic-radio-option/ic-radio-option.tsx @@ -247,13 +247,29 @@ export class RadioOption { this.selected = this.initiallySelected; }; + private getRadioOptionId = (): string => { + const radioGroups = Array.from( + this.el.ownerDocument.querySelectorAll("ic-radio-group") + ) as HTMLIcRadioGroupElement[]; + const matchingGroupLabels = radioGroups.filter( + (radioGroup) => radioGroup.label === this.groupLabel + ).length; + const groupIdentifier = + matchingGroupLabels > 1 && this.name + ? `${this.groupLabel}-${this.name}` + : this.groupLabel; + + return `ic-radio-option-${ + isPropDefined(this.label) || this.value + }-${groupIdentifier}`; + }; + render() { const { additionalFieldDisplay, disabled, dynamicText, form, - groupLabel, handleClick, handleKeyDown, hasAdditionalField, @@ -264,7 +280,7 @@ export class RadioOption { theme, } = this; - const id = `ic-radio-option-${isPropDefined(label) || value}-${groupLabel}`; + const id = this.getRadioOptionId(); return (