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

Open
wants to merge 1 commit into
base: file-dropzone-design
Choose a base branch
from

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.37 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 101.6 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 95.45 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.35 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.58 KiB 44.89 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.15 KiB 10.48 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 e09c607

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..fae77a144 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);
+        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 e09c607

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..78f61b3e5 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,25 @@
       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));
       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 +1790,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..8d4fa72a9 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,25 @@ 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));
     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 +1784,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..5755301d1 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,25 @@
       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));
       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 +617,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..75deb2845 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,25 @@ 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));
     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 +611,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..266c56ace 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,25 @@ 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));
     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 +140,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 e09c607

@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
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
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.

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