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 (