Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SPIKE] Label <button> with aria-labelledby (label + parts) #5647

Conversation

romaricpascal
Copy link
Member

@romaricpascal romaricpascal commented Jan 23, 2025

Adds the relevant IDs, as well as elements for commas to break down the label announcement between each parts and label the button with:

<LABEL>, <PSEUDO_BUTTON>, <STATUS>

The accessible name is announced OK, both when focusing and after having selected a file in:

  • VoiceOver + Safari
  • NVDA (2024.4) + Chrome (132)
  • NVDA (2024.4) + Firefox (134)

Thoughts

Main thing that's not ideal is that we need two comma elements if we want to break the announcement between each part, as the the Accessible Name and Description Computation algorithm (4.3.1, 2B) will only add the accessible name of an element with a given ID in aria-labelledby once, even if it appears multiple times. It's likely similar to what we need when we use govuk-visually-hidden content, though.

Actually, one of the two comma could be govuk-visually-hidden at the end of the pseudo-button, which would separate it from the status when CSS fails to load.

@romaricpascal romaricpascal marked this pull request as draft January 23, 2025 17:44
Copy link

github-actions bot commented Jan 23, 2025

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 120.03 KiB
dist/govuk-frontend-development.min.js 47.22 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 101.34 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 95.2 KiB
packages/govuk-frontend/dist/govuk/all.mjs 1.32 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 1.74 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 120.02 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 47.21 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.55 KiB
packages/govuk-frontend/dist/govuk/init.mjs 7.5 KiB

Modules

File Size (bundled) Size (minified)
all.mjs 89.33 KiB 44.75 KiB
accordion.mjs 26.58 KiB 13.41 KiB
button.mjs 9.09 KiB 3.78 KiB
character-count.mjs 25.39 KiB 10.9 KiB
checkboxes.mjs 7.81 KiB 3.42 KiB
error-summary.mjs 10.99 KiB 4.54 KiB
exit-this-page.mjs 20.2 KiB 10.34 KiB
file-upload.mjs 19.9 KiB 10.34 KiB
header.mjs 6.46 KiB 3.22 KiB
notification-banner.mjs 9.35 KiB 3.7 KiB
password-input.mjs 18.24 KiB 8.33 KiB
radios.mjs 6.81 KiB 2.98 KiB
service-navigation.mjs 6.44 KiB 3.26 KiB
skip-link.mjs 6.4 KiB 2.76 KiB
tabs.mjs 12.04 KiB 6.67 KiB

View stats and visualisations on the review app


Action run for 91caafe

Copy link

github-actions bot commented Jan 23, 2025

JavaScript changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index 374ce22cc..888e0081f 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -291,7 +291,7 @@ class Accordion extends ConfigurableComponent {
         });
         const o = document.createElement("button");
         o.setAttribute("type", "button"), o.setAttribute("aria-controls", `${this.$root.id}-content-${e+1}`);
-        for (const d of Array.from(n.attributes)) "id" !== d.name && o.setAttribute(d.name, d.value);
+        for (const u of Array.from(n.attributes)) "id" !== u.name && o.setAttribute(u.name, u.value);
         const r = document.createElement("span");
         r.classList.add(this.sectionHeadingTextClass), r.id = n.id;
         const a = document.createElement("span");
@@ -301,8 +301,8 @@ class Accordion extends ConfigurableComponent {
         const c = document.createElement("span");
         c.classList.add(this.sectionShowHideToggleFocusClass), l.appendChild(c);
         const h = document.createElement("span"),
-            u = document.createElement("span");
-        if (u.classList.add(this.upChevronIconClass), c.appendChild(u), h.classList.add(this.sectionShowHideTextClass), c.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), s) {
+            d = document.createElement("span");
+        if (d.classList.add(this.upChevronIconClass), c.appendChild(d), h.classList.add(this.sectionShowHideTextClass), c.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), s) {
             const t = document.createElement("span"),
                 e = document.createElement("span");
             e.classList.add(this.sectionSummaryFocusClass), t.appendChild(e);
@@ -754,15 +754,22 @@ class FileUpload extends ConfigurableComponent {
         if (!this.$root.id.length) throw new ElementError(formatErrorMessage(FileUpload, "Form field must specify an `id`."));
         this.id = this.$root.id, this.i18n = new I18n(this.config.i18n, {
             locale: closestAttributeValue(this.$root, "lang")
-        }), this.$label = this.findLabel(), this.$root.id = `${this.id}-input`;
-        const n = document.createElement("div");
-        n.className = "govuk-file-upload-wrapper";
-        const i = document.createElement("button");
-        i.classList.add("govuk-file-upload__button"), i.type = "button", i.id = this.id;
-        const s = document.createElement("span");
-        s.className = "govuk-button govuk-button--secondary govuk-file-upload__pseudo-button", s.innerText = this.i18n.t("selectFilesButton"), s.setAttribute("aria-hidden", "true"), i.appendChild(s), i.addEventListener("click", this.onClick.bind(this));
-        const o = document.createElement("span");
-        o.className = "govuk-body govuk-file-upload__status", o.innerText = this.i18n.t("filesSelectedDefault"), o.setAttribute("aria-hidden", "true"), i.appendChild(o), i.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")}, ${this.i18n.t("filesSelectedDefault")}`), n.insertAdjacentElement("beforeend", i), this.$root.insertAdjacentElement("afterend", n), this.$root.setAttribute("tabindex", "-1"), this.$root.setAttribute("aria-hidden", "true"), n.insertAdjacentElement("afterbegin", this.$root), this.$wrapper = n, this.$button = i, this.$status = o, this.$root.addEventListener("change", this.onChange.bind(this)), this.updateDisabledState(), this.observeDisabledState(), this.$root.addEventListener("change", this.onChange.bind(this)), this.$announcements = document.createElement("span"), this.$announcements.classList.add("govuk-file-upload-announcements"), this.$announcements.classList.add("govuk-visually-hidden"), this.$announcements.setAttribute("aria-live", "assertive"), this.$wrapper.insertAdjacentElement("afterend", this.$announcements), this.$wrapper.addEventListener("drop", this.hideDropZone.bind(this)), document.addEventListener("dragenter", this.updateDropzoneVisibility.bind(this)), document.addEventListener("dragenter", (() => {
+        });
+        const n = this.findLabel(),
+            i = document.createElement("span");
+        i.id = `${this.$root.id}-label`, Array.from(n.childNodes).forEach((t => i.appendChild(t))), n.innerText = "", n.appendChild(i), this.$root.id = `${this.id}-input`;
+        const s = document.createElement("div");
+        s.className = "govuk-file-upload-wrapper";
+        const o = document.createElement("button");
+        o.classList.add("govuk-file-upload__button"), o.type = "button", o.id = this.id;
+        const r = document.createElement("span");
+        r.className = "govuk-button govuk-button--secondary govuk-file-upload__pseudo-button", r.innerText = this.i18n.t("selectFilesButton"), r.setAttribute("aria-hidden", "true"), r.id = `${this.id}-button`, o.appendChild(r), o.addEventListener("click", this.onClick.bind(this));
+        const a = document.createElement("span");
+        a.className = "govuk-body govuk-file-upload__status", a.innerText = this.i18n.t("filesSelectedDefault"), a.setAttribute("aria-hidden", "true"), a.id = `${this.id}-status`;
+        const l = document.createElement("span");
+        l.innerText = ", ", l.setAttribute("hidden", ""), l.id = `${this.id}-comma`, s.appendChild(l);
+        const c = document.createElement("span");
+        c.innerText = ", ", c.setAttribute("hidden", ""), c.id = `${this.id}-comma2`, s.appendChild(c), o.appendChild(a), o.setAttribute("aria-labelledby", `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`), s.insertAdjacentElement("beforeend", o), this.$root.insertAdjacentElement("afterend", s), this.$root.setAttribute("tabindex", "-1"), this.$root.setAttribute("aria-hidden", "true"), s.insertAdjacentElement("afterbegin", this.$root), this.$wrapper = s, this.$button = o, this.$status = a, this.$root.addEventListener("change", this.onChange.bind(this)), this.updateDisabledState(), this.observeDisabledState(), this.$root.addEventListener("change", this.onChange.bind(this)), this.$announcements = document.createElement("span"), this.$announcements.classList.add("govuk-file-upload-announcements"), this.$announcements.classList.add("govuk-visually-hidden"), this.$announcements.setAttribute("aria-live", "assertive"), this.$wrapper.insertAdjacentElement("afterend", this.$announcements), this.$wrapper.addEventListener("drop", this.hideDropZone.bind(this)), document.addEventListener("dragenter", this.updateDropzoneVisibility.bind(this)), document.addEventListener("dragenter", (() => {
             this.enteredAnotherElement = !0
         })), document.addEventListener("dragleave", (() => {
             this.enteredAnotherElement || this.hideDropZone(), this.enteredAnotherElement = !1
@@ -782,7 +789,7 @@ class FileUpload extends ConfigurableComponent {
         const t = this.$root.files.length;
         this.$status.innerText = 0 === t ? this.i18n.t("filesSelectedDefault") : 1 === t ? this.$root.files[0].name : this.i18n.t("filesSelected", {
             count: t
-        }), this.$button.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")}, ${this.$status.innerText}`)
+        })
     }
     findLabel() {
         const t = document.querySelector(`label[for="${this.$root.id}"]`);

Action run for 91caafe

Copy link

github-actions bot commented Jan 23, 2025

Other changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index a6d922cdd..226f74b87 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1682,7 +1682,12 @@
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
-      this.$label = this.findLabel();
+      const $label = this.findLabel();
+      const $labelWrapper = document.createElement('span');
+      $labelWrapper.id = `${this.$root.id}-label`;
+      Array.from($label.childNodes).forEach($child => $labelWrapper.appendChild($child));
+      $label.innerText = '';
+      $label.appendChild($labelWrapper);
       this.$root.id = `${this.id}-input`;
       const $wrapper = document.createElement('div');
       $wrapper.className = 'govuk-file-upload-wrapper';
@@ -1694,14 +1699,26 @@
       buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
       buttonSpan.innerText = this.i18n.t('selectFilesButton');
       buttonSpan.setAttribute('aria-hidden', 'true');
+      buttonSpan.id = `${this.id}-button`;
       $button.appendChild(buttonSpan);
       $button.addEventListener('click', this.onClick.bind(this));
       const $status = document.createElement('span');
       $status.className = 'govuk-body govuk-file-upload__status';
       $status.innerText = this.i18n.t('filesSelectedDefault');
       $status.setAttribute('aria-hidden', 'true');
+      $status.id = `${this.id}-status`;
+      const $comma = document.createElement('span');
+      $comma.innerText = ', ';
+      $comma.setAttribute('hidden', '');
+      $comma.id = `${this.id}-comma`;
+      $wrapper.appendChild($comma);
+      const $comma2 = document.createElement('span');
+      $comma2.innerText = ', ';
+      $comma2.setAttribute('hidden', '');
+      $comma2.id = `${this.id}-comma2`;
+      $wrapper.appendChild($comma2);
       $button.appendChild($status);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+      $button.setAttribute('aria-labelledby', `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`);
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
       this.$root.setAttribute('tabindex', '-1');
@@ -1768,7 +1785,6 @@
           count: fileCount
         });
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
     }
     findLabel() {
       const $label = document.querySelector(`label[for="${this.$root.id}"]`);
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 62c861a22..cccb07a6d 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1676,7 +1676,12 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    const $labelWrapper = document.createElement('span');
+    $labelWrapper.id = `${this.$root.id}-label`;
+    Array.from($label.childNodes).forEach($child => $labelWrapper.appendChild($child));
+    $label.innerText = '';
+    $label.appendChild($labelWrapper);
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
@@ -1688,14 +1693,26 @@ class FileUpload extends ConfigurableComponent {
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
     buttonSpan.setAttribute('aria-hidden', 'true');
+    buttonSpan.id = `${this.id}-button`;
     $button.appendChild(buttonSpan);
     $button.addEventListener('click', this.onClick.bind(this));
     const $status = document.createElement('span');
     $status.className = 'govuk-body govuk-file-upload__status';
     $status.innerText = this.i18n.t('filesSelectedDefault');
     $status.setAttribute('aria-hidden', 'true');
+    $status.id = `${this.id}-status`;
+    const $comma = document.createElement('span');
+    $comma.innerText = ', ';
+    $comma.setAttribute('hidden', '');
+    $comma.id = `${this.id}-comma`;
+    $wrapper.appendChild($comma);
+    const $comma2 = document.createElement('span');
+    $comma2.innerText = ', ';
+    $comma2.setAttribute('hidden', '');
+    $comma2.id = `${this.id}-comma2`;
+    $wrapper.appendChild($comma2);
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.setAttribute('aria-labelledby', `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`);
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -1762,7 +1779,6 @@ class FileUpload extends ConfigurableComponent {
         count: fileCount
       });
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
   }
   findLabel() {
     const $label = document.querySelector(`label[for="${this.$root.id}"]`);
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
index 905b3c3e4..77fc24b7d 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
@@ -509,7 +509,12 @@
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
-      this.$label = this.findLabel();
+      const $label = this.findLabel();
+      const $labelWrapper = document.createElement('span');
+      $labelWrapper.id = `${this.$root.id}-label`;
+      Array.from($label.childNodes).forEach($child => $labelWrapper.appendChild($child));
+      $label.innerText = '';
+      $label.appendChild($labelWrapper);
       this.$root.id = `${this.id}-input`;
       const $wrapper = document.createElement('div');
       $wrapper.className = 'govuk-file-upload-wrapper';
@@ -521,14 +526,26 @@
       buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
       buttonSpan.innerText = this.i18n.t('selectFilesButton');
       buttonSpan.setAttribute('aria-hidden', 'true');
+      buttonSpan.id = `${this.id}-button`;
       $button.appendChild(buttonSpan);
       $button.addEventListener('click', this.onClick.bind(this));
       const $status = document.createElement('span');
       $status.className = 'govuk-body govuk-file-upload__status';
       $status.innerText = this.i18n.t('filesSelectedDefault');
       $status.setAttribute('aria-hidden', 'true');
+      $status.id = `${this.id}-status`;
+      const $comma = document.createElement('span');
+      $comma.innerText = ', ';
+      $comma.setAttribute('hidden', '');
+      $comma.id = `${this.id}-comma`;
+      $wrapper.appendChild($comma);
+      const $comma2 = document.createElement('span');
+      $comma2.innerText = ', ';
+      $comma2.setAttribute('hidden', '');
+      $comma2.id = `${this.id}-comma2`;
+      $wrapper.appendChild($comma2);
       $button.appendChild($status);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+      $button.setAttribute('aria-labelledby', `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`);
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
       this.$root.setAttribute('tabindex', '-1');
@@ -595,7 +612,6 @@
           count: fileCount
         });
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
     }
     findLabel() {
       const $label = document.querySelector(`label[for="${this.$root.id}"]`);
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
index 72724da01..b3b729e1f 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
@@ -503,7 +503,12 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    const $labelWrapper = document.createElement('span');
+    $labelWrapper.id = `${this.$root.id}-label`;
+    Array.from($label.childNodes).forEach($child => $labelWrapper.appendChild($child));
+    $label.innerText = '';
+    $label.appendChild($labelWrapper);
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
@@ -515,14 +520,26 @@ class FileUpload extends ConfigurableComponent {
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
     buttonSpan.setAttribute('aria-hidden', 'true');
+    buttonSpan.id = `${this.id}-button`;
     $button.appendChild(buttonSpan);
     $button.addEventListener('click', this.onClick.bind(this));
     const $status = document.createElement('span');
     $status.className = 'govuk-body govuk-file-upload__status';
     $status.innerText = this.i18n.t('filesSelectedDefault');
     $status.setAttribute('aria-hidden', 'true');
+    $status.id = `${this.id}-status`;
+    const $comma = document.createElement('span');
+    $comma.innerText = ', ';
+    $comma.setAttribute('hidden', '');
+    $comma.id = `${this.id}-comma`;
+    $wrapper.appendChild($comma);
+    const $comma2 = document.createElement('span');
+    $comma2.innerText = ', ';
+    $comma2.setAttribute('hidden', '');
+    $comma2.id = `${this.id}-comma2`;
+    $wrapper.appendChild($comma2);
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.setAttribute('aria-labelledby', `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`);
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -589,7 +606,6 @@ class FileUpload extends ConfigurableComponent {
         count: fileCount
       });
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
   }
   findLabel() {
     const $label = document.querySelector(`label[for="${this.$root.id}"]`);
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
index 3eeaf0c49..2a2515b60 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
@@ -32,7 +32,12 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    const $labelWrapper = document.createElement('span');
+    $labelWrapper.id = `${this.$root.id}-label`;
+    Array.from($label.childNodes).forEach($child => $labelWrapper.appendChild($child));
+    $label.innerText = '';
+    $label.appendChild($labelWrapper);
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
@@ -44,14 +49,26 @@ class FileUpload extends ConfigurableComponent {
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
     buttonSpan.setAttribute('aria-hidden', 'true');
+    buttonSpan.id = `${this.id}-button`;
     $button.appendChild(buttonSpan);
     $button.addEventListener('click', this.onClick.bind(this));
     const $status = document.createElement('span');
     $status.className = 'govuk-body govuk-file-upload__status';
     $status.innerText = this.i18n.t('filesSelectedDefault');
     $status.setAttribute('aria-hidden', 'true');
+    $status.id = `${this.id}-status`;
+    const $comma = document.createElement('span');
+    $comma.innerText = ', ';
+    $comma.setAttribute('hidden', '');
+    $comma.id = `${this.id}-comma`;
+    $wrapper.appendChild($comma);
+    const $comma2 = document.createElement('span');
+    $comma2.innerText = ', ';
+    $comma2.setAttribute('hidden', '');
+    $comma2.id = `${this.id}-comma2`;
+    $wrapper.appendChild($comma2);
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.setAttribute('aria-labelledby', `${this.id}-label ${this.id}-comma ${this.id}-button ${this.id}-comma2 ${this.id}-status`);
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -118,7 +135,6 @@ class FileUpload extends ConfigurableComponent {
         count: fileCount
       });
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
   }
   findLabel() {
     const $label = document.querySelector(`label[for="${this.$root.id}"]`);

Action run for 91caafe

@romaricpascal romaricpascal force-pushed the spike-file-upload-aria-labelledby-parts branch from 8a93675 to 45e5d0a Compare January 23, 2025 19:00
…ble name

Adds the relevant IDs, as well as elements for commas to break down the label announcement between each parts.
@romaricpascal romaricpascal force-pushed the spike-file-upload-aria-labelledby-parts branch from 45e5d0a to 91caafe Compare January 24, 2025 13:44
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-5647 January 24, 2025 13:44 Inactive
@romaricpascal romaricpascal changed the title SPIKE - Use aria-labelledby referencing parts of the button [SPIKE] Label <button> with aria-labelledby (label + parts) Jan 27, 2025
@romaricpascal
Copy link
Member Author

Chosen implementation was #5652, which enables referencing both the visible text of the button and the label, reducing content duplication and making sure the accessible name uses browser-translated text when users use their browser's translation features.

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