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

Label <button> with aria-labelledby (label + itself) #5652

Merged

Conversation

romaricpascal
Copy link
Member

Reference the <label> and the <button> itself so there's no duplication of content, ensuring that when translated, the accessible name matches what's visible on the screen.

Copy link

github-actions bot commented Jan 24, 2025

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 120.55 KiB
dist/govuk-frontend-development.min.js 47.4 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 101.65 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 95.5 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.54 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 47.39 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.63 KiB 44.93 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 20.2 KiB 10.51 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 0115249

Copy link

github-actions bot commented Jan 24, 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 2e0a33ee4..009712df5 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -754,23 +754,25 @@ 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("span");
-        i.className = "govuk-visually-hidden", i.innerText = ", ";
+        });
+        const n = this.findLabel();
+        n.setAttribute("for", `${this.id}-input`), n.id || (n.id = `${this.id}-label`), this.$root.id = `${this.id}-input`;
+        const i = document.createElement("div");
+        i.className = "govuk-file-upload-wrapper";
         const s = document.createElement("button");
         s.classList.add("govuk-file-upload__button"), s.type = "button", s.id = this.id;
         const o = this.$root.getAttribute("aria-describedby");
         o && s.setAttribute("aria-describedby", o);
         const r = document.createElement("span");
-        r.className = "govuk-body govuk-file-upload__status", r.innerText = this.i18n.t("filesSelectedDefault"), r.setAttribute("aria-hidden", "true"), r.classList.add("govuk-file-upload__status--empty"), s.appendChild(r), s.appendChild(i.cloneNode(!0));
+        r.className = "govuk-body govuk-file-upload__status", r.innerText = this.i18n.t("filesSelectedDefault"), r.classList.add("govuk-file-upload__status--empty"), s.appendChild(r);
         const a = document.createElement("span");
-        a.className = "govuk-file-upload__pseudo-button-container";
+        a.className = "govuk-visually-hidden", a.innerText = ", ", a.id = `${this.id}-comma`, s.appendChild(a);
         const l = document.createElement("span");
-        l.className = "govuk-button govuk-button--secondary govuk-file-upload__pseudo-button", l.innerText = this.i18n.t("selectFilesButton"), l.setAttribute("aria-hidden", "true"), a.appendChild(l), a.appendChild(i.cloneNode(!0));
+        l.className = "govuk-file-upload__pseudo-button-container";
         const c = document.createElement("span");
-        c.className = "govuk-body govuk-file-upload__instruction", c.innerText = this.i18n.t("instruction"), a.appendChild(c), s.appendChild(a), s.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")} ${this.i18n.t("instruction")}, ${r.innerText}`), s.addEventListener("click", this.onClick.bind(this)), n.insertAdjacentElement("beforeend", s), 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 = s, this.$status = r, this.$root.addEventListener("change", this.onChange.bind(this)), this.updateDisabledState(), this.observeDisabledState(), 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", (() => {
+        c.className = "govuk-button govuk-button--secondary govuk-file-upload__pseudo-button", c.innerText = this.i18n.t("selectFilesButton"), l.appendChild(c), l.insertAdjacentText("beforeend", " ");
+        const h = document.createElement("span");
+        h.className = "govuk-body govuk-file-upload__instruction", h.innerText = this.i18n.t("instruction"), l.appendChild(h), s.appendChild(l), s.setAttribute("aria-labelledby", `${n.id} ${a.id} ${s.id}`), s.addEventListener("click", this.onClick.bind(this)), i.insertAdjacentElement("beforeend", s), this.$root.insertAdjacentElement("afterend", i), this.$root.setAttribute("tabindex", "-1"), this.$root.setAttribute("aria-hidden", "true"), i.insertAdjacentElement("afterbegin", this.$root), this.$wrapper = i, this.$button = s, this.$status = r, this.$root.addEventListener("change", this.onChange.bind(this)), this.updateDisabledState(), this.observeDisabledState(), 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
@@ -790,7 +792,7 @@ class FileUpload extends ConfigurableComponent {
         const t = this.$root.files.length;
         0 === t ? (this.$status.innerText = this.i18n.t("filesSelectedDefault"), this.$status.classList.add("govuk-file-upload__status--empty")) : (this.$status.innerText = 1 === t ? this.$root.files[0].name : this.i18n.t("filesSelected", {
             count: t
-        }), this.$status.classList.remove("govuk-file-upload__status--empty")), this.$button.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")} ${this.i18n.t("instruction")}, ${this.$status.innerText}`)
+        }), this.$status.classList.remove("govuk-file-upload__status--empty"))
     }
     findLabel() {
         const t = document.querySelector(`label[for="${this.$root.id}"]`);

Action run for 0115249

Copy link

github-actions bot commented Jan 24, 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 a58b5df65..3185bbaa6 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1682,13 +1682,14 @@
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
-      this.$label = this.findLabel();
+      const $label = this.findLabel();
+      $label.setAttribute('for', `${this.id}-input`);
+      if (!$label.id) {
+        $label.id = `${this.id}-label`;
+      }
       this.$root.id = `${this.id}-input`;
       const $wrapper = document.createElement('div');
       $wrapper.className = 'govuk-file-upload-wrapper';
-      const commaSpan = document.createElement('span');
-      commaSpan.className = 'govuk-visually-hidden';
-      commaSpan.innerText = ', ';
       const $button = document.createElement('button');
       $button.classList.add('govuk-file-upload__button');
       $button.type = 'button';
@@ -1700,24 +1701,26 @@
       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.classList.add('govuk-file-upload__status--empty');
       $button.appendChild($status);
-      $button.appendChild(commaSpan.cloneNode(true));
+      const commaSpan = document.createElement('span');
+      commaSpan.className = 'govuk-visually-hidden';
+      commaSpan.innerText = ', ';
+      commaSpan.id = `${this.id}-comma`;
+      $button.appendChild(commaSpan);
       const containerSpan = document.createElement('span');
       containerSpan.className = 'govuk-file-upload__pseudo-button-container';
       const buttonSpan = document.createElement('span');
       buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
       buttonSpan.innerText = this.i18n.t('selectFilesButton');
-      buttonSpan.setAttribute('aria-hidden', 'true');
       containerSpan.appendChild(buttonSpan);
-      containerSpan.appendChild(commaSpan.cloneNode(true));
+      containerSpan.insertAdjacentText('beforeend', ' ');
       const instructionSpan = document.createElement('span');
       instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
       instructionSpan.innerText = this.i18n.t('instruction');
       containerSpan.appendChild(instructionSpan);
       $button.appendChild(containerSpan);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+      $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
       $button.addEventListener('click', this.onClick.bind(this));
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
@@ -1788,7 +1791,6 @@
         }
         this.$status.classList.remove('govuk-file-upload__status--empty');
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${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 589c4ccf6..58b709ec3 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1676,13 +1676,14 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    $label.setAttribute('for', `${this.id}-input`);
+    if (!$label.id) {
+      $label.id = `${this.id}-label`;
+    }
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
-    const commaSpan = document.createElement('span');
-    commaSpan.className = 'govuk-visually-hidden';
-    commaSpan.innerText = ', ';
     const $button = document.createElement('button');
     $button.classList.add('govuk-file-upload__button');
     $button.type = 'button';
@@ -1694,24 +1695,26 @@ class FileUpload extends ConfigurableComponent {
     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.classList.add('govuk-file-upload__status--empty');
     $button.appendChild($status);
-    $button.appendChild(commaSpan.cloneNode(true));
+    const commaSpan = document.createElement('span');
+    commaSpan.className = 'govuk-visually-hidden';
+    commaSpan.innerText = ', ';
+    commaSpan.id = `${this.id}-comma`;
+    $button.appendChild(commaSpan);
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload__pseudo-button-container';
     const buttonSpan = document.createElement('span');
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
-    buttonSpan.setAttribute('aria-hidden', 'true');
     containerSpan.appendChild(buttonSpan);
-    containerSpan.appendChild(commaSpan.cloneNode(true));
+    containerSpan.insertAdjacentText('beforeend', ' ');
     const instructionSpan = document.createElement('span');
     instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
     instructionSpan.innerText = this.i18n.t('instruction');
     containerSpan.appendChild(instructionSpan);
     $button.appendChild(containerSpan);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
     $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
@@ -1782,7 +1785,6 @@ class FileUpload extends ConfigurableComponent {
       }
       this.$status.classList.remove('govuk-file-upload__status--empty');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${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 0b4d1db88..f733355f6 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,13 +509,14 @@
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
-      this.$label = this.findLabel();
+      const $label = this.findLabel();
+      $label.setAttribute('for', `${this.id}-input`);
+      if (!$label.id) {
+        $label.id = `${this.id}-label`;
+      }
       this.$root.id = `${this.id}-input`;
       const $wrapper = document.createElement('div');
       $wrapper.className = 'govuk-file-upload-wrapper';
-      const commaSpan = document.createElement('span');
-      commaSpan.className = 'govuk-visually-hidden';
-      commaSpan.innerText = ', ';
       const $button = document.createElement('button');
       $button.classList.add('govuk-file-upload__button');
       $button.type = 'button';
@@ -527,24 +528,26 @@
       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.classList.add('govuk-file-upload__status--empty');
       $button.appendChild($status);
-      $button.appendChild(commaSpan.cloneNode(true));
+      const commaSpan = document.createElement('span');
+      commaSpan.className = 'govuk-visually-hidden';
+      commaSpan.innerText = ', ';
+      commaSpan.id = `${this.id}-comma`;
+      $button.appendChild(commaSpan);
       const containerSpan = document.createElement('span');
       containerSpan.className = 'govuk-file-upload__pseudo-button-container';
       const buttonSpan = document.createElement('span');
       buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
       buttonSpan.innerText = this.i18n.t('selectFilesButton');
-      buttonSpan.setAttribute('aria-hidden', 'true');
       containerSpan.appendChild(buttonSpan);
-      containerSpan.appendChild(commaSpan.cloneNode(true));
+      containerSpan.insertAdjacentText('beforeend', ' ');
       const instructionSpan = document.createElement('span');
       instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
       instructionSpan.innerText = this.i18n.t('instruction');
       containerSpan.appendChild(instructionSpan);
       $button.appendChild(containerSpan);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+      $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
       $button.addEventListener('click', this.onClick.bind(this));
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
@@ -615,7 +618,6 @@
         }
         this.$status.classList.remove('govuk-file-upload__status--empty');
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${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 ce4dc45ba..5b6188ebb 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,13 +503,14 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    $label.setAttribute('for', `${this.id}-input`);
+    if (!$label.id) {
+      $label.id = `${this.id}-label`;
+    }
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
-    const commaSpan = document.createElement('span');
-    commaSpan.className = 'govuk-visually-hidden';
-    commaSpan.innerText = ', ';
     const $button = document.createElement('button');
     $button.classList.add('govuk-file-upload__button');
     $button.type = 'button';
@@ -521,24 +522,26 @@ class FileUpload extends ConfigurableComponent {
     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.classList.add('govuk-file-upload__status--empty');
     $button.appendChild($status);
-    $button.appendChild(commaSpan.cloneNode(true));
+    const commaSpan = document.createElement('span');
+    commaSpan.className = 'govuk-visually-hidden';
+    commaSpan.innerText = ', ';
+    commaSpan.id = `${this.id}-comma`;
+    $button.appendChild(commaSpan);
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload__pseudo-button-container';
     const buttonSpan = document.createElement('span');
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
-    buttonSpan.setAttribute('aria-hidden', 'true');
     containerSpan.appendChild(buttonSpan);
-    containerSpan.appendChild(commaSpan.cloneNode(true));
+    containerSpan.insertAdjacentText('beforeend', ' ');
     const instructionSpan = document.createElement('span');
     instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
     instructionSpan.innerText = this.i18n.t('instruction');
     containerSpan.appendChild(instructionSpan);
     $button.appendChild(containerSpan);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
     $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
@@ -609,7 +612,6 @@ class FileUpload extends ConfigurableComponent {
       }
       this.$status.classList.remove('govuk-file-upload__status--empty');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${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 cee8c3418..445fcf481 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,13 +32,14 @@ class FileUpload extends ConfigurableComponent {
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
-    this.$label = this.findLabel();
+    const $label = this.findLabel();
+    $label.setAttribute('for', `${this.id}-input`);
+    if (!$label.id) {
+      $label.id = `${this.id}-label`;
+    }
     this.$root.id = `${this.id}-input`;
     const $wrapper = document.createElement('div');
     $wrapper.className = 'govuk-file-upload-wrapper';
-    const commaSpan = document.createElement('span');
-    commaSpan.className = 'govuk-visually-hidden';
-    commaSpan.innerText = ', ';
     const $button = document.createElement('button');
     $button.classList.add('govuk-file-upload__button');
     $button.type = 'button';
@@ -50,24 +51,26 @@ class FileUpload extends ConfigurableComponent {
     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.classList.add('govuk-file-upload__status--empty');
     $button.appendChild($status);
-    $button.appendChild(commaSpan.cloneNode(true));
+    const commaSpan = document.createElement('span');
+    commaSpan.className = 'govuk-visually-hidden';
+    commaSpan.innerText = ', ';
+    commaSpan.id = `${this.id}-comma`;
+    $button.appendChild(commaSpan);
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload__pseudo-button-container';
     const buttonSpan = document.createElement('span');
     buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload__pseudo-button';
     buttonSpan.innerText = this.i18n.t('selectFilesButton');
-    buttonSpan.setAttribute('aria-hidden', 'true');
     containerSpan.appendChild(buttonSpan);
-    containerSpan.appendChild(commaSpan.cloneNode(true));
+    containerSpan.insertAdjacentText('beforeend', ' ');
     const instructionSpan = document.createElement('span');
     instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
     instructionSpan.innerText = this.i18n.t('instruction');
     containerSpan.appendChild(instructionSpan);
     $button.appendChild(containerSpan);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.setAttribute('aria-labelledby', `${$label.id} ${commaSpan.id} ${$button.id}`);
     $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
@@ -138,7 +141,6 @@ class FileUpload extends ConfigurableComponent {
       }
       this.$status.classList.remove('govuk-file-upload__status--empty');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${this.$status.innerText}`);
   }
   findLabel() {
     const $label = document.querySelector(`label[for="${this.$root.id}"]`);

Action run for 0115249

@romaricpascal romaricpascal changed the title Label the <button> using aria-labelledby [SPIKE] Label <button> with aria-labelledby (label + itself) Jan 27, 2025
@patrickpatrickpatrick patrickpatrickpatrick force-pushed the file-dropzone-design branch 13 times, most recently from 21c6ecf to ede849e Compare January 30, 2025 11:26
@romaricpascal romaricpascal force-pushed the spike-file-upload-aria-labelledby branch from 95e45e7 to e09c607 Compare January 30, 2025 11:53
@romaricpascal romaricpascal marked this pull request as ready for review January 30, 2025 12:15
Copy link
Contributor

@patrickpatrickpatrick patrickpatrickpatrick left a comment

Choose a reason for hiding this comment

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

Changes look to me. We had a pairing session in which we worked through it together.

@romaricpascal romaricpascal changed the title [SPIKE] Label <button> with aria-labelledby (label + itself) Label <button> with aria-labelledby (label + itself) Jan 30, 2025
@selfthinker
Copy link

I've done another round of testing. Here are my findings:

  • When dropping a file it gets announced as "left dropzone" by a screen reader. Something else should be announced, ideally at least the file name. I suspect that can most easily be handled by just moving the focus to the button.
  • After selecting a file, the file name a screen reader announces is not always correct. Sometimes it still saying "no file chosen" or the previously selected file. That's most probably due to the screen reader's virtual buffer needing some time to process the changes.
  • When disabling CSS (or using userstyles), there should be a space between "choose file" and "or drop file". It currently reads "choose fileor drop file".
  • The file name's background is sometimes white within a white box, so there is no visible file name area. Is that intentional?
  • I don't think this is important but just mentioning for completeness' sake: The hint doesn't get read out by NVDA when arrowing or after a file has been selected. I assume that is just how aria-describedby is handled by NVDA in these cases and nothing to worry about.

Base automatically changed from file-dropzone-design to spike-enhanced-file-upload January 31, 2025 11:46
Reference the `<label>` and the `<button>` itself so there's no duplication
of content, ensuring that when translated, the accessible name matches what's
visible on the screen.
@romaricpascal romaricpascal force-pushed the spike-file-upload-aria-labelledby branch from e09c607 to 0115249 Compare January 31, 2025 14:35
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-5652 January 31, 2025 14:35 Inactive
@romaricpascal
Copy link
Member Author

@patrickpatrickpatrick Added an extra space between the button and the instruction so that when CSS is disabled, the text reads OK.

Screenshot of the File Upload component without CSS, with the button correctly reading 'Choose file or drop file'

@patrickpatrickpatrick
Copy link
Contributor

@patrickpatrickpatrick Added an extra space between the button and the instruction so that when CSS is disabled, the text reads OK.

Screenshot of the File Upload component without CSS, with the button correctly reading 'Choose file or drop file'

Looks good to me.

@romaricpascal romaricpascal merged commit 908487b into spike-enhanced-file-upload Jan 31, 2025
46 checks passed
@romaricpascal romaricpascal deleted the spike-file-upload-aria-labelledby branch January 31, 2025 17:42
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.

4 participants