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

File input new dropzone design #5642

Open
wants to merge 3 commits into
base: spike-enhanced-file-upload
Choose a base branch
from

Conversation

patrickpatrickpatrick
Copy link
Contributor

@patrickpatrickpatrick patrickpatrickpatrick commented Jan 22, 2025

What

Implement the new file input design.

Why

Fixes #5611

@patrickpatrickpatrick patrickpatrickpatrick changed the base branch from main to use-output January 22, 2025 16:09
Copy link

github-actions bot commented Jan 22, 2025

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 120.38 KiB
dist/govuk-frontend-development.min.js 47.64 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 101.82 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 95.68 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.36 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 47.63 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.81 KiB 45.16 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.38 KiB 10.75 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 00cb7d9

@patrickpatrickpatrick patrickpatrickpatrick changed the title file dropzone design File input new dropzone design Jan 22, 2025
Copy link

github-actions bot commented Jan 22, 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..6b859649b 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -754,15 +754,21 @@ 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`;
+        }), this.$label = this.findLabel(), this.$root.id = `${this.id}-input`, this.$label.setAttribute("for", `${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 i = document.createElement("span");
+        i.className = "govuk-visually-hidden", i.innerText = ", ";
+        const s = document.createElement("button");
+        s.classList.add("govuk-file-upload__button"), s.type = "button", s.id = this.id;
         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", (() => {
+        o.className = "govuk-body govuk-file-upload__status", o.innerText = this.i18n.t("filesSelectedDefault"), o.setAttribute("aria-hidden", "true"), this.$root.files.length || o.classList.add("govuk-tag--light-blue"), s.appendChild(o), s.appendChild(i.cloneNode(!0));
+        const r = document.createElement("span");
+        r.className = "govuk-file-upload__pseudo-button-container";
+        const a = document.createElement("span");
+        a.className = "govuk-button govuk-button--secondary govuk-file-upload__pseudo-button", a.innerText = this.i18n.t("selectFilesButton"), a.setAttribute("aria-hidden", "true"), r.appendChild(a), r.appendChild(i.cloneNode(!0));
+        const l = document.createElement("span");
+        l.className = "govuk-body govuk-file-upload__instruction", l.innerText = this.i18n.t("instruction"), r.appendChild(l), s.appendChild(r), s.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")} ${this.i18n.t("instruction")}, ${o.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 = 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", (() => {
             this.enteredAnotherElement = !0
         })), document.addEventListener("dragleave", (() => {
             this.enteredAnotherElement || this.hideDropZone(), this.enteredAnotherElement = !1
@@ -780,9 +786,9 @@ class FileUpload extends ConfigurableComponent {
     }
     onChange() {
         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", {
+        0 === t ? (this.$status.innerText = this.i18n.t("filesSelectedDefault"), this.$status.classList.add("govuk-tag--light-blue")) : (this.$status.innerText = 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}`)
+        }), this.$status.classList.remove("govuk-tag--light-blue")), this.$button.setAttribute("aria-label", `${this.$label.innerText}, ${this.i18n.t("selectFilesButton")} ${this.i18n.t("instruction")}, ${this.$status.innerText}`)
     }
     findLabel() {
         const t = document.querySelector(`label[for="${this.$root.id}"]`);
@@ -815,7 +821,8 @@ FileUpload.moduleName = "govuk-file-upload", FileUpload.defaults = Object.freeze
             other: "%{count} files chosen"
         },
         dropZoneEntered: "Entered drop zone",
-        dropZoneLeft: "Left drop zone"
+        dropZoneLeft: "Left drop zone",
+        instruction: "or drop file"
     }
 }), FileUpload.schema = Object.freeze({
     properties: {

Action run for 00cb7d9

Copy link

github-actions bot commented Jan 22, 2025

Stylesheets changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
index 9417707ca..343ab1ce7 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
@@ -3385,25 +3385,23 @@ screen and (forced-colors:active) {
 }
 
 .govuk-file-upload-wrapper {
-    display: inline-flex;
-    align-items: baseline;
-    position: relative
+    display: block;
+    position: relative;
+    z-index: 0
 }
 
 .govuk-file-upload-wrapper--show-dropzone {
-    margin: -12px;
-    padding: 10px;
-    border: 2px dashed #0b0c0c;
+    outline: 3px solid #fd0;
     background-color: #fff
 }
 
-.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__pseudo-button,
-.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__status {
-    pointer-events: none
+.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload {
+    z-index: 1
 }
 
 .govuk-file-upload-wrapper .govuk-file-upload {
     position: absolute;
+    z-index: -1;
     top: 0;
     left: 0;
     width: 100%;
@@ -3415,61 +3413,85 @@ screen and (forced-colors:active) {
 
 .govuk-file-upload__pseudo-button {
     width: auto;
-    margin-bottom: 0;
-    flex-grow: 0;
     flex-shrink: 0
 }
 
-.govuk-file-upload__status {
-    margin-bottom: 0;
+.govuk-file-upload__pseudo-button-container>* {
+    margin-bottom: 0
+}
+
+.govuk-file-upload__instruction {
     margin-left: 10px
 }
 
-.govuk-file-upload__button:focus {
-    outline: none
+.govuk-file-upload__status {
+    display: block;
+    margin-bottom: 10px;
+    padding: 15px 10px;
+    text-align: left
 }
 
-.govuk-file-upload__button:focus .govuk-file-upload__pseudo-button {
-    outline: 3px solid transparent;
-    background-color: #fd0;
-    box-shadow: 0 2px 0 #0b0c0c
+.govuk-file-upload__pseudo-button-container {
+    display: flex;
+    align-items: baseline
 }
 
-.govuk-file-upload__button:focus .govuk-file-upload__pseudo-button:hover {
-    border-color: #fd0;
-    outline: 3px solid transparent;
-    background-color: #f3f2f1;
-    box-shadow: inset 0 0 0 1px #fd0
+.govuk-file-upload__button {
+    width: 100%;
+    padding: 15px 18px;
+    border: 2px dashed #b1b4b6;
+    background-color: #fff;
+    cursor: pointer
 }
 
-.govuk-file-upload__button:active .govuk-file-upload__pseudo-button:hover {
-    background-color: #c2c2c1
+@media (min-width:40.0625em) {
+    .govuk-file-upload__button {
+        padding: 15px 23px
+    }
 }
 
-.govuk-file-upload__button {
-    align-items: center;
-    display: flex;
-    padding: 0;
-    border: 0;
-    background-color: transparent
+.govuk-file-upload__button:hover {
+    border-color: #8e9092
 }
 
-.govuk-file-upload:disabled+.govuk-file-upload__button {
-    pointer-events: none
+.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__button,
+.govuk-file-upload__button:hover,
+.govuk-file-upload__button:hover .govuk-file-upload__pseudo-button {
+    background-color: #f3f2f1
 }
 
-.govuk-file-upload:disabled+.govuk-file-upload__button .govuk-file-upload__pseudo-button {
-    opacity: .5
+.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__status,
+.govuk-file-upload__button:focus .govuk-file-upload__status,
+.govuk-file-upload__button:hover .govuk-file-upload__status {
+    background-color: #d2e2f1
+}
+
+.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__button {
+    border-color: transparent
+}
+
+.govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__pseudo-button {
+    background-color: #fff
 }
 
-.govuk-file-upload:disabled+.govuk-file-upload__button .govuk-file-upload__pseudo-button:hover {
+.govuk-file-upload__button:active,
+.govuk-file-upload__button:focus {
+    border: 2px solid #0b0c0c;
+    outline: 3px solid #fd0;
+    outline-offset: 0;
     background-color: #f3f2f1;
-    cursor: not-allowed
+    box-shadow: inset 0 0 0 2px
 }
 
-.govuk-file-upload:disabled+.govuk-file-upload__button .govuk-file-upload__pseudo-button:active {
-    top: 0;
-    box-shadow: 0 2px 0 #666
+.govuk-file-upload__button:focus .govuk-file-upload__pseudo-button {
+    background-color: #fd0
+}
+
+.govuk-file-upload__button:focus:hover .govuk-file-upload__pseudo-button {
+    border-color: #fd0;
+    outline: 3px solid transparent;
+    background-color: #f3f2f1;
+    box-shadow: inset 0 0 0 1px #fd0
 }
 
 .govuk-footer {

Action run for 00cb7d9

Copy link

github-actions bot commented Jan 22, 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..05abbd7dc 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1684,24 +1684,40 @@
       });
       this.$label = this.findLabel();
       this.$root.id = `${this.id}-input`;
+      this.$label.setAttribute('for', `${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';
       $button.id = this.id;
-      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');
-      $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');
+      if (!this.$root.files.length) {
+        $status.classList.add('govuk-tag--light-blue');
+      }
       $button.appendChild($status);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+      $button.appendChild(commaSpan.cloneNode(true));
+      const buttonParentSpan = document.createElement('span');
+      buttonParentSpan.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');
+      buttonParentSpan.appendChild(buttonSpan);
+      buttonParentSpan.appendChild(commaSpan.cloneNode(true));
+      const instructionSpan = document.createElement('span');
+      instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
+      instructionSpan.innerText = this.i18n.t('instruction');
+      buttonParentSpan.appendChild(instructionSpan);
+      $button.appendChild(buttonParentSpan);
+      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+      $button.addEventListener('click', this.onClick.bind(this));
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
       this.$root.setAttribute('tabindex', '-1');
@@ -1761,14 +1777,18 @@
       const fileCount = this.$root.files.length;
       if (fileCount === 0) {
         this.$status.innerText = this.i18n.t('filesSelectedDefault');
-      } else if (fileCount === 1) {
-        this.$status.innerText = this.$root.files[0].name;
+        this.$status.classList.add('govuk-tag--light-blue');
       } else {
-        this.$status.innerText = this.i18n.t('filesSelected', {
-          count: fileCount
-        });
+        if (fileCount === 1) {
+          this.$status.innerText = this.$root.files[0].name;
+        } else {
+          this.$status.innerText = this.i18n.t('filesSelected', {
+            count: fileCount
+          });
+        }
+        this.$status.classList.remove('govuk-tag--light-blue');
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
+      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}"]`);
@@ -1810,7 +1830,8 @@
         other: '%{count} files chosen'
       },
       dropZoneEntered: 'Entered drop zone',
-      dropZoneLeft: 'Left drop zone'
+      dropZoneLeft: 'Left drop zone',
+      instruction: 'or drop file'
     }
   });
   FileUpload.schema = Object.freeze({
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 62c861a22..e2c8aba80 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1678,24 +1678,40 @@ class FileUpload extends ConfigurableComponent {
     });
     this.$label = this.findLabel();
     this.$root.id = `${this.id}-input`;
+    this.$label.setAttribute('for', `${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';
     $button.id = this.id;
-    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');
-    $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');
+    if (!this.$root.files.length) {
+      $status.classList.add('govuk-tag--light-blue');
+    }
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.appendChild(commaSpan.cloneNode(true));
+    const buttonParentSpan = document.createElement('span');
+    buttonParentSpan.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');
+    buttonParentSpan.appendChild(buttonSpan);
+    buttonParentSpan.appendChild(commaSpan.cloneNode(true));
+    const instructionSpan = document.createElement('span');
+    instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
+    instructionSpan.innerText = this.i18n.t('instruction');
+    buttonParentSpan.appendChild(instructionSpan);
+    $button.appendChild(buttonParentSpan);
+    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -1755,14 +1771,18 @@ class FileUpload extends ConfigurableComponent {
     const fileCount = this.$root.files.length;
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
-    } else if (fileCount === 1) {
-      this.$status.innerText = this.$root.files[0].name;
+      this.$status.classList.add('govuk-tag--light-blue');
     } else {
-      this.$status.innerText = this.i18n.t('filesSelected', {
-        count: fileCount
-      });
+      if (fileCount === 1) {
+        this.$status.innerText = this.$root.files[0].name;
+      } else {
+        this.$status.innerText = this.i18n.t('filesSelected', {
+          count: fileCount
+        });
+      }
+      this.$status.classList.remove('govuk-tag--light-blue');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
+    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}"]`);
@@ -1804,7 +1824,8 @@ FileUpload.defaults = Object.freeze({
       other: '%{count} files chosen'
     },
     dropZoneEntered: 'Entered drop zone',
-    dropZoneLeft: 'Left drop zone'
+    dropZoneLeft: 'Left drop zone',
+    instruction: 'or drop file'
   }
 });
 FileUpload.schema = Object.freeze({
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/_index.scss b/packages/govuk-frontend/dist/govuk/components/file-upload/_index.scss
index 4dc478173..9b7a57aed 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/_index.scss
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/_index.scss
@@ -3,6 +3,7 @@
 @import "../label/index";
 
 @include govuk-exports("govuk/component/file-upload") {
+  $file-upload-border-width: 2px;
   $component-padding: govuk-spacing(1);
 
   .govuk-file-upload {
@@ -48,34 +49,25 @@
   }
 
   .govuk-file-upload-wrapper {
-    display: inline-flex;
-    align-items: baseline;
+    display: block;
     position: relative;
+    z-index: 0;
   }
 
   .govuk-file-upload-wrapper--show-dropzone {
-    $dropzone-padding: govuk-spacing(2);
-    $dropzone-offset: $dropzone-padding + $govuk-border-width-form-element;
-
-    // Add negative margins to all sides so that content doesn't jump due to
-    // the addition of the padding and border.
-    margin: -$dropzone-offset;
-    padding: $dropzone-padding;
-    border: $govuk-border-width-form-element dashed $govuk-input-border-colour;
+    outline: 3px solid #ffdd00;
     background-color: $govuk-body-background-colour;
+  }
 
-    .govuk-file-upload__pseudo-button,
-    .govuk-file-upload__status {
-      // When the dropzone is hovered over, make these aspects not accept
-      // mouse events, so dropped files fall through to the input beneath them
-      pointer-events: none;
-    }
+  .govuk-file-upload-wrapper--show-dropzone .govuk-file-upload {
+    z-index: 1;
   }
 
   .govuk-file-upload-wrapper .govuk-file-upload {
+    position: absolute;
     // Make the native control take up the entire space of the element, but
     // invisible and behind the other elements until we need it
-    position: absolute;
+    z-index: -1;
     top: 0;
     left: 0;
     width: 100%;
@@ -87,61 +79,90 @@
 
   .govuk-file-upload__pseudo-button {
     width: auto;
-    margin-bottom: 0;
-    flex-grow: 0;
     flex-shrink: 0;
   }
 
-  .govuk-file-upload__status {
+  .govuk-file-upload__pseudo-button-container > * {
     margin-bottom: 0;
+  }
+
+  .govuk-file-upload__instruction {
     margin-left: govuk-spacing(2);
   }
-}
 
-.govuk-file-upload__button:focus {
-  outline: none;
-}
+  .govuk-file-upload__status {
+    display: block;
+    margin-bottom: govuk-spacing(2);
+    padding: govuk-spacing(3) govuk-spacing(2);
+    text-align: left;
+  }
 
-.govuk-file-upload__button:focus .govuk-file-upload__pseudo-button {
-  outline: 3px solid transparent;
-  background-color: $govuk-focus-colour;
-  box-shadow: 0 2px 0 govuk-colour("black");
-}
+  .govuk-file-upload__pseudo-button-container {
+    display: flex;
+    align-items: baseline;
+  }
 
-.govuk-file-upload__button:focus .govuk-file-upload__pseudo-button:hover {
-  border-color: $govuk-focus-colour;
-  outline: 3px solid transparent;
-  background-color: govuk-colour("light-grey");
-  box-shadow: inset 0 0 0 1px $govuk-focus-colour;
-}
+  .govuk-file-upload__button {
+    width: 100%;
+    // align the padding to be same as notification banner and error summary accounting for the thicker borders
+    padding: govuk-spacing(3) (govuk-spacing(3) + $govuk-border-width - $file-upload-border-width);
+    border: $file-upload-border-width govuk-colour("mid-grey") dashed;
+    background-color: govuk-colour("white");
+    cursor: pointer;
+
+    @include govuk-media-query($from: tablet) {
+      padding: govuk-spacing(3) (govuk-spacing(4) + $govuk-border-width - $file-upload-border-width);
+    }
+  }
 
-.govuk-file-upload__button:active .govuk-file-upload__pseudo-button:hover {
-  background-color: govuk-shade(govuk-colour("light-grey"), 20%);
-}
+  .govuk-file-upload__button:hover {
+    border-color: govuk-shade(govuk-colour("mid-grey"), 20%);
+  }
 
-.govuk-file-upload__button {
-  align-items: center;
-  display: flex;
-  padding: 0;
-  border: 0;
-  background-color: transparent;
-}
+  .govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__button,
+  .govuk-file-upload__button:hover,
+  .govuk-file-upload__button:hover .govuk-file-upload__pseudo-button {
+    background-color: govuk-colour("light-grey");
+  }
 
-.govuk-file-upload:disabled + .govuk-file-upload__button {
-  pointer-events: none;
-}
+  .govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__status,
+  .govuk-file-upload__button:hover .govuk-file-upload__status,
+  .govuk-file-upload__button:focus .govuk-file-upload__status {
+    background-color: govuk-tint(govuk-colour("blue"), 80%);
+  }
+
+  .govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__button {
+    border-color: transparent;
+  }
 
-.govuk-file-upload:disabled + .govuk-file-upload__button .govuk-file-upload__pseudo-button {
-  opacity: (0.5);
+  .govuk-file-upload-wrapper--show-dropzone .govuk-file-upload__pseudo-button {
+    background-color: govuk-colour("white");
+  }
 
-  &:hover {
+  .govuk-file-upload__button:active,
+  .govuk-file-upload__button:focus {
+    border: 2px solid govuk-colour("black");
+    outline: $govuk-focus-width solid $govuk-focus-colour;
+    // Ensure outline appears outside of the element
+    outline-offset: 0;
     background-color: govuk-colour("light-grey");
-    cursor: not-allowed;
+    // Double the border by adding its width again. Use `box-shadow` for this
+    // instead of changing `border-width` - this is for consistency with
+    // components such as textarea where we avoid changing `border-width` as
+    // it will change the element size. Also, `outline` cannot be utilised
+    // here as it is already used for the yellow focus state.
+    box-shadow: inset 0 0 0 $govuk-border-width-form-element;
   }
 
-  &:active {
-    top: 0;
-    box-shadow: 0 $govuk-border-width-form-element 0 govuk-shade(govuk-colour("white"), 60%); // s0
+  .govuk-file-upload__button:focus .govuk-file-upload__pseudo-button {
+    background-color: $govuk-focus-colour;
+  }
+
+  .govuk-file-upload__button:focus:hover .govuk-file-upload__pseudo-button {
+    border-color: $govuk-focus-colour;
+    outline: 3px solid transparent;
+    background-color: govuk-colour("light-grey");
+    box-shadow: inset 0 0 0 1px $govuk-focus-colour;
   }
 }
 
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..a138ee559 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
@@ -511,24 +511,40 @@
       });
       this.$label = this.findLabel();
       this.$root.id = `${this.id}-input`;
+      this.$label.setAttribute('for', `${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';
       $button.id = this.id;
-      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');
-      $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');
+      if (!this.$root.files.length) {
+        $status.classList.add('govuk-tag--light-blue');
+      }
       $button.appendChild($status);
-      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+      $button.appendChild(commaSpan.cloneNode(true));
+      const buttonParentSpan = document.createElement('span');
+      buttonParentSpan.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');
+      buttonParentSpan.appendChild(buttonSpan);
+      buttonParentSpan.appendChild(commaSpan.cloneNode(true));
+      const instructionSpan = document.createElement('span');
+      instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
+      instructionSpan.innerText = this.i18n.t('instruction');
+      buttonParentSpan.appendChild(instructionSpan);
+      $button.appendChild(buttonParentSpan);
+      $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+      $button.addEventListener('click', this.onClick.bind(this));
       $wrapper.insertAdjacentElement('beforeend', $button);
       this.$root.insertAdjacentElement('afterend', $wrapper);
       this.$root.setAttribute('tabindex', '-1');
@@ -588,14 +604,18 @@
       const fileCount = this.$root.files.length;
       if (fileCount === 0) {
         this.$status.innerText = this.i18n.t('filesSelectedDefault');
-      } else if (fileCount === 1) {
-        this.$status.innerText = this.$root.files[0].name;
+        this.$status.classList.add('govuk-tag--light-blue');
       } else {
-        this.$status.innerText = this.i18n.t('filesSelected', {
-          count: fileCount
-        });
+        if (fileCount === 1) {
+          this.$status.innerText = this.$root.files[0].name;
+        } else {
+          this.$status.innerText = this.i18n.t('filesSelected', {
+            count: fileCount
+          });
+        }
+        this.$status.classList.remove('govuk-tag--light-blue');
       }
-      this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
+      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}"]`);
@@ -637,7 +657,8 @@
         other: '%{count} files chosen'
       },
       dropZoneEntered: 'Entered drop zone',
-      dropZoneLeft: 'Left drop zone'
+      dropZoneLeft: 'Left drop zone',
+      instruction: 'or drop file'
     }
   });
   FileUpload.schema = Object.freeze({
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..d53f5ef9d 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
@@ -505,24 +505,40 @@ class FileUpload extends ConfigurableComponent {
     });
     this.$label = this.findLabel();
     this.$root.id = `${this.id}-input`;
+    this.$label.setAttribute('for', `${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';
     $button.id = this.id;
-    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');
-    $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');
+    if (!this.$root.files.length) {
+      $status.classList.add('govuk-tag--light-blue');
+    }
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.appendChild(commaSpan.cloneNode(true));
+    const buttonParentSpan = document.createElement('span');
+    buttonParentSpan.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');
+    buttonParentSpan.appendChild(buttonSpan);
+    buttonParentSpan.appendChild(commaSpan.cloneNode(true));
+    const instructionSpan = document.createElement('span');
+    instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
+    instructionSpan.innerText = this.i18n.t('instruction');
+    buttonParentSpan.appendChild(instructionSpan);
+    $button.appendChild(buttonParentSpan);
+    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -582,14 +598,18 @@ class FileUpload extends ConfigurableComponent {
     const fileCount = this.$root.files.length;
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
-    } else if (fileCount === 1) {
-      this.$status.innerText = this.$root.files[0].name;
+      this.$status.classList.add('govuk-tag--light-blue');
     } else {
-      this.$status.innerText = this.i18n.t('filesSelected', {
-        count: fileCount
-      });
+      if (fileCount === 1) {
+        this.$status.innerText = this.$root.files[0].name;
+      } else {
+        this.$status.innerText = this.i18n.t('filesSelected', {
+          count: fileCount
+        });
+      }
+      this.$status.classList.remove('govuk-tag--light-blue');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
+    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}"]`);
@@ -631,7 +651,8 @@ FileUpload.defaults = Object.freeze({
       other: '%{count} files chosen'
     },
     dropZoneEntered: 'Entered drop zone',
-    dropZoneLeft: 'Left drop zone'
+    dropZoneLeft: 'Left drop zone',
+    instruction: 'or drop file'
   }
 });
 FileUpload.schema = Object.freeze({
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..8cae2bfa2 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
@@ -34,24 +34,40 @@ class FileUpload extends ConfigurableComponent {
     });
     this.$label = this.findLabel();
     this.$root.id = `${this.id}-input`;
+    this.$label.setAttribute('for', `${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';
     $button.id = this.id;
-    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');
-    $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');
+    if (!this.$root.files.length) {
+      $status.classList.add('govuk-tag--light-blue');
+    }
     $button.appendChild($status);
-    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.i18n.t('filesSelectedDefault')}`);
+    $button.appendChild(commaSpan.cloneNode(true));
+    const buttonParentSpan = document.createElement('span');
+    buttonParentSpan.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');
+    buttonParentSpan.appendChild(buttonSpan);
+    buttonParentSpan.appendChild(commaSpan.cloneNode(true));
+    const instructionSpan = document.createElement('span');
+    instructionSpan.className = 'govuk-body govuk-file-upload__instruction';
+    instructionSpan.innerText = this.i18n.t('instruction');
+    buttonParentSpan.appendChild(instructionSpan);
+    $button.appendChild(buttonParentSpan);
+    $button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')} ${this.i18n.t('instruction')}, ${$status.innerText}`);
+    $button.addEventListener('click', this.onClick.bind(this));
     $wrapper.insertAdjacentElement('beforeend', $button);
     this.$root.insertAdjacentElement('afterend', $wrapper);
     this.$root.setAttribute('tabindex', '-1');
@@ -111,14 +127,18 @@ class FileUpload extends ConfigurableComponent {
     const fileCount = this.$root.files.length;
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
-    } else if (fileCount === 1) {
-      this.$status.innerText = this.$root.files[0].name;
+      this.$status.classList.add('govuk-tag--light-blue');
     } else {
-      this.$status.innerText = this.i18n.t('filesSelected', {
-        count: fileCount
-      });
+      if (fileCount === 1) {
+        this.$status.innerText = this.$root.files[0].name;
+      } else {
+        this.$status.innerText = this.i18n.t('filesSelected', {
+          count: fileCount
+        });
+      }
+      this.$status.classList.remove('govuk-tag--light-blue');
     }
-    this.$button.setAttribute('aria-label', `${this.$label.innerText}, ${this.i18n.t('selectFilesButton')}, ${this.$status.innerText}`);
+    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}"]`);
@@ -160,7 +180,8 @@ FileUpload.defaults = Object.freeze({
       other: '%{count} files chosen'
     },
     dropZoneEntered: 'Entered drop zone',
-    dropZoneLeft: 'Left drop zone'
+    dropZoneLeft: 'Left drop zone',
+    instruction: 'or drop file'
   }
 });
 FileUpload.schema = Object.freeze({
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/fixtures.json b/packages/govuk-frontend/dist/govuk/components/file-upload/fixtures.json
index 74645e1bc..dacd5ee56 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/fixtures.json
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/fixtures.json
@@ -168,6 +168,7 @@
                 },
                 "multiple": true,
                 "selectFilesButtonText": "Dewiswch ffeil",
+                "instructionText": "neu ollwng ffeil",
                 "filesSelectedDefaultText": "Dim ffeiliau wedi'u dewis",
                 "filesSelectedText": {
                     "other": "%{count} ffeil wedi'u dewis",
@@ -178,7 +179,7 @@
             "description": "",
             "previewLayoutModifiers": [],
             "screenshot": false,
-            "html": "<div class=\"govuk-form-group\">\n  <label class=\"govuk-label\" for=\"file-upload-1\">\n    Llwythwch ffeil i fyny\n  </label>\n  <input class=\"govuk-file-upload\" id=\"file-upload-1\" name=\"file-upload-1\" type=\"file\" data-module=\"govuk-file-upload\" multiple data-i18n.select-files-button=\"Dewiswch ffeil\" data-i18n.files-selected-default=\"Dim ffeiliau wedi&#39;u dewis\" data-i18n.files-selected.other=\"%{count} ffeil wedi&#39;u dewis\" data-i18n.files-selected.one=\"%{count} ffeil wedi&#39;i dewis\">\n</div>"
+            "html": "<div class=\"govuk-form-group\">\n  <label class=\"govuk-label\" for=\"file-upload-1\">\n    Llwythwch ffeil i fyny\n  </label>\n  <input class=\"govuk-file-upload\" id=\"file-upload-1\" name=\"file-upload-1\" type=\"file\" data-module=\"govuk-file-upload\" multiple data-i18n.select-files-button=\"Dewiswch ffeil\" data-i18n.files-selected-default=\"Dim ffeiliau wedi&#39;u dewis\" data-i18n.files-selected.other=\"%{count} ffeil wedi&#39;u dewis\" data-i18n.files-selected.one=\"%{count} ffeil wedi&#39;i dewis\" data-i18n.instruction.0=\"n\" data-i18n.instruction.1=\"e\" data-i18n.instruction.2=\"u\" data-i18n.instruction.3=\" \" data-i18n.instruction.4=\"o\" data-i18n.instruction.5=\"l\" data-i18n.instruction.6=\"l\" data-i18n.instruction.7=\"w\" data-i18n.instruction.8=\"n\" data-i18n.instruction.9=\"g\" data-i18n.instruction.10=\" \" data-i18n.instruction.11=\"f\" data-i18n.instruction.12=\"f\" data-i18n.instruction.13=\"e\" data-i18n.instruction.14=\"i\" data-i18n.instruction.15=\"l\">\n</div>"
         },
         {
             "name": "with value",
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/macro-options.json b/packages/govuk-frontend/dist/govuk/components/file-upload/macro-options.json
index deb9c3604..1da949426 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/macro-options.json
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/macro-options.json
@@ -122,6 +122,12 @@
         "required": false,
         "description": "The text of the button that opens the file picker. JavaScript enhanced version of the component only. Default is \"Choose file\"."
     },
+    {
+        "name": "instructionText",
+        "type": "string",
+        "required": false,
+        "description": "The text of the instruction text that follows the button that opens the file picker. JavaScript enhanced version of the component only. Default is \"or drop file\"."
+    },
     {
         "name": "filesSelected",
         "type": "object",

Action run for 00cb7d9

Base automatically changed from use-output to spike-enhanced-file-upload January 23, 2025 10:54
@patrickpatrickpatrick patrickpatrickpatrick marked this pull request as ready for review January 23, 2025 15:20
@CharlotteDowns
Copy link
Contributor

CharlotteDowns commented Jan 24, 2025

Nice work Patrick 💪🏻.

Design feedback

I think we need to use the default secondary button styling on focus, this would mean using #0b0c0c instead of #929191 on the box-shadow
Secondary button focus example

I also noticed that on hover and focus+hover that he secondary button doesn't take the hover background colour of #dbdad9.

The dragged-on state seems to use the yellow #fd0 instead of #0b0c0c for the border.

It should say 'or drop file' instead of 'instruction'

The padding on the should be 13px 23px 15px 24px; to ensure it's consistency with other elements in the Design System, for example, Notification banner etc.

@romaricpascal
Copy link
Member

Cheers Patrick, that's a good start! I've documented a couple of missed details in the design document (internal link).

The padding on the should be 13px 23px 15px 24px; to ensure it's consistency with other elements in the Design System, for example, Notification banner etc.

@CharlotteDowns It's a bit strange to have such variation in padding. Inspecting the Notification Banner on the Design System site, I'm seeing the following paddings:

  • .govuk-notification-banner__header: 2px 20px 5px (15px instead of 20px on narrow viewports)
  • .govuk-notification-banner__content: 20px (15px on narrow viewports)

Similarly on the Error Summary, the padding seems happily at 20px (15px on narrow viewports). What's the source of the values you're proposing?

@CharlotteDowns
Copy link
Contributor

CharlotteDowns commented Jan 24, 2025

@romaricpascal this could be my poor maths but I'm trying to recreate the same visual properties and alignment to the Notification banner and Error summary but working with a 2px border instead of a 5px border (used on those components). Maybe I should refer to the summary card styling instead 🤔, although that seems to only have a 1px border :(.

Button creation of file upload needs to be changed in line with the new
design. Includes new `instruction span` which has been added to the i18n
configuration.
New styles for file upload component. Includes adjusting the `z-index`
when the dropzone is toggled.
$status.setAttribute('aria-hidden', 'true')

if (!this.$root.files.length) {
$status.classList.add('govuk-tag--light-blue')
Copy link
Member

Choose a reason for hiding this comment

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

@patrickpatrickpatrick Missed that bit earlier. Rather than risking an unexpected change if we update the styles of the Tag component, let's isolate this with a class specific to the file-upload 😊

Same goes with the govuk-body in line 89 and 113, we can configure the font in the classes specific to the govuk-file-upload and avoid worrying about other styles brought by govuk-body (like the margin).

Copy link
Member

Choose a reason for hiding this comment

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

At the point the component is initialised files won't be filed as the page will just be loaded, so we can drop that if from the constructor altogether and only update the styles when the user selects a file 😊

Copy link

Rendered HTML changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/template-translated.html b/packages/govuk-frontend/dist/govuk/components/file-upload/template-translated.html
index a39fab0a5..598965d3b 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/template-translated.html
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/template-translated.html
@@ -2,5 +2,5 @@
   <label class="govuk-label" for="file-upload-1">
     Llwythwch ffeil i fyny
   </label>
-  <input class="govuk-file-upload" id="file-upload-1" name="file-upload-1" type="file" data-module="govuk-file-upload" multiple data-i18n.select-files-button="Dewiswch ffeil" data-i18n.files-selected-default="Dim ffeiliau wedi&#39;u dewis" data-i18n.files-selected.other="%{count} ffeil wedi&#39;u dewis" data-i18n.files-selected.one="%{count} ffeil wedi&#39;i dewis">
+  <input class="govuk-file-upload" id="file-upload-1" name="file-upload-1" type="file" data-module="govuk-file-upload" multiple data-i18n.select-files-button="Dewiswch ffeil" data-i18n.files-selected-default="Dim ffeiliau wedi&#39;u dewis" data-i18n.files-selected.other="%{count} ffeil wedi&#39;u dewis" data-i18n.files-selected.one="%{count} ffeil wedi&#39;i dewis" data-i18n.instruction.0="n" data-i18n.instruction.1="e" data-i18n.instruction.2="u" data-i18n.instruction.3=" " data-i18n.instruction.4="o" data-i18n.instruction.5="l" data-i18n.instruction.6="l" data-i18n.instruction.7="w" data-i18n.instruction.8="n" data-i18n.instruction.9="g" data-i18n.instruction.10=" " data-i18n.instruction.11="f" data-i18n.instruction.12="f" data-i18n.instruction.13="e" data-i18n.instruction.14="i" data-i18n.instruction.15="l">
 </div>

Action run for 00cb7d9

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