From 2c9a5bb471dd5e7469a7f2f412cf37df784e765e Mon Sep 17 00:00:00 2001 From: Boyan Rakilovski Date: Mon, 20 Jul 2026 15:49:30 +0300 Subject: [PATCH 1/2] feat(ui5-file-uploader): [prototype] replace aria-required with native required on shadow input Replace aria-required with the native required attribute on the internal to improve accessibility announcements in Chromium-based browsers (Chrome/Edge), where aria-required is no longer announced by NVDA/JAWS for file inputs exposed as button role in the accessibility tree. This is a prototype for manual AT testing on Windows with NVDA/JAWS before merging. A test page with four scenarios covering ElementInternals validity, shadow input form isolation, required announcement, and premature invalid announcement has been added to the test page. Relates to: https://github.com/UI5/webcomponents/issues/13777 Relates to: https://github.com/UI5/webcomponents/issues/13305 Relates to: https://github.com/UI5/webcomponents/issues/13808 --- packages/main/src/FileUploader.ts | 1 - packages/main/src/FileUploaderTemplate.tsx | 2 +- packages/main/test/pages/FileUploader.html | 124 ++++++++++++++++++++- 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/packages/main/src/FileUploader.ts b/packages/main/src/FileUploader.ts index 9f8f142188616..000cfe3f01f14 100644 --- a/packages/main/src/FileUploader.ts +++ b/packages/main/src/FileUploader.ts @@ -625,7 +625,6 @@ class FileUploader extends UI5Element implements IFormInputElement { get accInfo(): InputAccInfo { return { "ariaRoledescription": FileUploader.i18nBundle.getText(FILEUPLOADER_ROLE_DESCRIPTION), - "ariaRequired": this.required || undefined, "ariaInvalid": this.valueState === ValueState.Negative || undefined, "ariaHasPopup": "dialog", "ariaLabel": getAllAccessibleNameRefTexts(this) || getEffectiveAriaLabelText(this) || getAssociatedLabelForTexts(this) || undefined, diff --git a/packages/main/src/FileUploaderTemplate.tsx b/packages/main/src/FileUploaderTemplate.tsx index a54954d0ca889..3c888888d9cf2 100644 --- a/packages/main/src/FileUploaderTemplate.tsx +++ b/packages/main/src/FileUploaderTemplate.tsx @@ -28,12 +28,12 @@ export default function FileUploaderTemplate(this: FileUploader) { multiple={this.multiple} accept={this.accept} disabled={this.disabled} + required={this.required} title={this.inputTitle} aria-roledescription={this.accInfo.ariaRoledescription} aria-haspopup={this.accInfo.ariaHasPopup} aria-label={this.accInfo.ariaLabel} aria-description={this.accInfo.ariaDescription} - aria-required={this.accInfo.ariaRequired} aria-invalid={this.accInfo.ariaInvalid} aria-describedby={this.accInfo.ariaDescribedBy} onClick={this._onNativeInputClick} diff --git a/packages/main/test/pages/FileUploader.html b/packages/main/test/pages/FileUploader.html index 0797c162c5172..6f9e85e2d020e 100644 --- a/packages/main/test/pages/FileUploader.html +++ b/packages/main/test/pages/FileUploader.html @@ -102,8 +102,14 @@ Choose files: +
- Form validation + Scenario 1 — ElementInternals validity (UI5 checkValidity) +

Uses element.checkValidity() and element.reportValidity() on the ui5-file-uploader directly. Validation is driven by ElementInternals, not by the shadow <input>.

Choose files: @@ -112,7 +118,103 @@ Check Validity
+ + +
+ Scenario 2 — Native form submission & shadow input isolation +

+ The native <input type="file" required> inside Shadow DOM is not in form.elements and cannot independently block form submission.
+ The ui5-file-uploader custom element is form-associated via ElementInternals and does participate in native form constraint validation. +

+
+ Attachment (required):
+ +

+ Name (native required input):
+ +

+ +   + Inspect form.elements +
+ +
+ + +
+ Scenario 3 — AT announcement: "required" +

Tab to each file uploader and check whether your screen reader announces "required". The fix adds required as a native HTML attribute on the shadow input.

+ +
+
+

Required (no value state)
Expected: announces "required", no premature "invalid"

+ Attachment: + +
+ +
+

Not required
Expected: no "required" announcement

+ Attachment: + +
+
+
+ + +
+ Scenario 4 — Premature "invalid" announcement check +

Tab to each uploader before selecting any file and check whether the screen reader announces "invalid" prematurely.

+ +
+
+

Required, no value state
+ aria-invalid is absent → native valueMissing may surface
+ Expected: no premature "invalid" (button role in Chromium a11y tree suppresses it)

+ Attachment: + +
+ +
+

Required + Negative value state
+ aria-invalid="true" is explicitly set
+ Expected: announces "required" + "invalid"

+ Attachment: + +
This field is required
+
+
+ +
+

Required + file selected (valid state)
+ Expected: no "required" removal announcement anomalies after selection

+ Attachment: + +
+
+
+ From 96b86df86cd8f9c17c937a3e27ea9dedae19265e Mon Sep 17 00:00:00 2001 From: Boyan Rakilovski Date: Fri, 24 Jul 2026 12:51:58 +0300 Subject: [PATCH 2/2] fix(ui5-file-uploader): replace aria-required with native required on shadow input Issue: Chromium-based browsers (Chrome, Edge) no longer announce aria-required for because the element is exposed as a button role in the accessibility tree, and aria-required is not a valid property for that role. As a result, NVDA and JAWS users on Chrome/Edge did not hear a "required" announcement for required file uploader fields. Solution: Replace aria-required with the native required attribute on the shadow . To prevent the native valueMissing state from causing a premature "invalid entry" announcement before user interaction, aria-invalid is explicitly set to false for all value states except Negative, where aria-invalid="true" is intentionally preserved. This keeps invalid state communication fully under application control, consistent with how other UI5 form components behave. Fixes: https://github.com/UI5/webcomponents/issues/13777 Relates to: https://github.com/UI5/webcomponents/issues/13305 Relates to: https://github.com/UI5/webcomponents/issues/13808 --- .../main/cypress/specs/FileUploader.cy.tsx | 24 +++++- packages/main/src/FileUploader.ts | 2 +- packages/main/test/pages/FileUploader.html | 78 +++---------------- 3 files changed, 35 insertions(+), 69 deletions(-) diff --git a/packages/main/cypress/specs/FileUploader.cy.tsx b/packages/main/cypress/specs/FileUploader.cy.tsx index 280b0611b7d04..7bf66571f44d6 100644 --- a/packages/main/cypress/specs/FileUploader.cy.tsx +++ b/packages/main/cypress/specs/FileUploader.cy.tsx @@ -71,7 +71,29 @@ describe("API", () => { cy.get("[ui5-file-uploader]") .shadow() .find("input[type='file']") - .should("have.attr", "aria-required", "true"); + .as("nativeInput") + .should("have.attr", "required"); + + cy.get("@nativeInput") + .should("have.attr", "aria-invalid", "false"); + }); + + it("aria-invalid reflects value state", () => { + (["None", "Positive", "Critical", "Information"] as const).forEach(valueState => { + cy.mount(); + + cy.get("[ui5-file-uploader]") + .shadow() + .find("input[type='file']") + .should("have.attr", "aria-invalid", "false"); + }); + + cy.mount(); + + cy.get("[ui5-file-uploader]") + .shadow() + .find("input[type='file']") + .should("have.attr", "aria-invalid", "true"); }); it("accept property", () => { diff --git a/packages/main/src/FileUploader.ts b/packages/main/src/FileUploader.ts index 000cfe3f01f14..20491c83a9cce 100644 --- a/packages/main/src/FileUploader.ts +++ b/packages/main/src/FileUploader.ts @@ -625,7 +625,7 @@ class FileUploader extends UI5Element implements IFormInputElement { get accInfo(): InputAccInfo { return { "ariaRoledescription": FileUploader.i18nBundle.getText(FILEUPLOADER_ROLE_DESCRIPTION), - "ariaInvalid": this.valueState === ValueState.Negative || undefined, + "ariaInvalid": this.valueState === ValueState.Negative, "ariaHasPopup": "dialog", "ariaLabel": getAllAccessibleNameRefTexts(this) || getEffectiveAriaLabelText(this) || getAssociatedLabelForTexts(this) || undefined, "ariaDescription": getAllAccessibleDescriptionRefTexts(this) || getEffectiveAriaDescriptionText(this) || undefined, diff --git a/packages/main/test/pages/FileUploader.html b/packages/main/test/pages/FileUploader.html index 6f9e85e2d020e..5a284550c4552 100644 --- a/packages/main/test/pages/FileUploader.html +++ b/packages/main/test/pages/FileUploader.html @@ -102,14 +102,8 @@ Choose files: -
- Scenario 1 — ElementInternals validity (UI5 checkValidity) -

Uses element.checkValidity() and element.reportValidity() on the ui5-file-uploader directly. Validation is driven by ElementInternals, not by the shadow <input>.

+ Form validation
Choose files: @@ -119,27 +113,13 @@
-
- Scenario 2 — Native form submission & shadow input isolation -

- The native <input type="file" required> inside Shadow DOM is not in form.elements and cannot independently block form submission.
- The ui5-file-uploader custom element is form-associated via ElementInternals and does participate in native form constraint validation. -

+ Form submission & shadow input isolation
Attachment (required):


- Name (native required input):
+ Name:


@@ -149,72 +129,37 @@
-
- Scenario 3 — AT announcement: "required" -

Tab to each file uploader and check whether your screen reader announces "required". The fix adds required as a native HTML attribute on the shadow input.

- -
+ Required — AT announcement +
-

Required (no value state)
Expected: announces "required", no premature "invalid"

- Attachment: + Required:
-
-

Not required
Expected: no "required" announcement

- Attachment: + Not required:
-
- Scenario 4 — Premature "invalid" announcement check -

Tab to each uploader before selecting any file and check whether the screen reader announces "invalid" prematurely.

- + Required with value states
-

Required, no value state
- aria-invalid is absent → native valueMissing may surface
- Expected: no premature "invalid" (button role in Chromium a11y tree suppresses it)

- Attachment: + Required, no value state:
-
-

Required + Negative value state
- aria-invalid="true" is explicitly set
- Expected: announces "required" + "invalid"

- Attachment: + Required, Negative:
This field is required
- -
-

Required + file selected (valid state)
- Expected: no "required" removal announcement anomalies after selection

- Attachment: - -