diff --git a/core/modules/file/file.es6.js b/core/modules/file/file.es6.js
index dc8a80742..bcbf83c26 100644
--- a/core/modules/file/file.es6.js
+++ b/core/modules/file/file.es6.js
@@ -19,42 +19,23 @@
    *   Detaches validation for file extensions.
    */
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach(context, settings) {
+    attach: function (context) {
       const $context = $(context);
       let elements;

-      function initFileValidation(selector) {
-        $context
-          .find(selector)
-          .once('fileValidate')
-          .on(
-            'change.fileValidate',
-            { extensions: elements[selector] },
-            Drupal.file.validateExtension,
-          );
-      }
-
-      if (settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(initFileValidation);
-      }
+      $context.find('[data-drupal-validate-extensions]')
+        .once('fileValidate')
+        .on('change.fileValidate', Drupal.file.validateExtension);
     },
     detach(context, settings, trigger) {
       const $context = $(context);
-      let elements;

-      function removeFileValidation(selector) {
-        $context
-          .find(selector)
+      if (trigger === 'unload') {
+          $context.find('[data-drupal-validate-extensions]')
           .removeOnce('fileValidate')
           .off('change.fileValidate', Drupal.file.validateExtension);
       }
-
-      if (trigger === 'unload' && settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(removeFileValidation);
-      }
-    },
+    }
   };

   /**
@@ -160,13 +141,17 @@
       $('.file-upload-js-error').remove();

       // Add client side validation for the input[type=file].
-      const extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
-      if (extensionPattern.length > 1 && this.value.length > 0) {
-        const acceptableMatch = new RegExp(`\\.(${extensionPattern})$`, 'gi');
-        if (!acceptableMatch.test(this.value)) {
-          const error = Drupal.t(
-            'The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.',
-            {
+      var validExtensions = JSON.parse(event.target.getAttribute('data-drupal-validate-extensions'));
+      if (validExtensions.length > 1 && event.target.value.length > 0) {
+        var acceptableMatch = new RegExp('\\.(' + validExtensions.join('|') + ')$', 'i');
+        var testSubjects = event.target.files;
+        if (!testSubjects) {
+          testSubjects = [{name: event.target.value}];
+        }
+        var invalidFiles = [];
+        for (var i = 0; i < testSubjects.length; i++) {
+          var testSubject = testSubjects[i];
+          if (!acceptableMatch.test(testSubject.name)) {
               // According to the specifications of HTML5, a file upload control
               // should not reveal the real local path to the file that a user
               // has selected. Some web browsers implement this restriction by
@@ -174,15 +159,11 @@
               // confusion by leaving the user thinking perhaps Drupal could not
               // find the file because it messed up the file path. To avoid this
               // confusion, therefore, we strip out the bogus fakepath string.
-              '%filename': this.value.replace('C:\\fakepath\\', ''),
-              '%extensions': extensionPattern.replace(/\|/g, ', '),
-            },
-          );
-          $(this)
-            .closest('div.js-form-managed-file')
-            .prepend(
-              `<div class="messages messages--error file-upload-js-error" aria-live="polite">${error}</div>`,
-            );
+              invalidFiles.push(testSubject.name.replace('C:\\fakepath\\', ''));
+          }
+        }
+        if (invalidFiles.length) {
+          $(this).closest('div.js-form-managed-file').prepend(Drupal.theme('fileValidationError', invalidFiles, validExtensions));
           this.value = '';
           // Cancel all other change event handlers.
           event.stopImmediatePropagation();
@@ -300,4 +281,35 @@
       );
     },
   };
+
+  /**
+   * Error message of file validation.
+   *
+   * @param {Array.<string>} invalidFiles
+   *   Array of invalid filenames.
+   * @param {string} validExtensions
+   *   List of accepted files extensions.
+   *
+   * @return {string}
+   *   Error message.
+   */
+  Drupal.theme.fileValidationError = function (invalidFiles, validExtensions) {
+    var error = Drupal.formatPlural(invalidFiles.length, 'The selected file %filename cannot be uploaded.', '@count of the selected files cannot be uploaded.', {
+      '%filename': invalidFiles[0]
+    });
+    if (invalidFiles.length > 1) {
+      error += '<ul>';
+      invalidFiles.forEach(function (file) {
+        error += '<li>' + Drupal.formatString('%filename', {'%filename': file}) + '</li>';
+      });
+      error += '</ul>';
+    }
+    error += ' ';
+    error += Drupal.t('Only files with the following extensions are allowed: %extensions.', {
+      '%extensions': validExtensions.join(', ')
+    });
+
+    return '<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>';
+  };
+
 })(jQuery, Drupal);
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 79aa8288c..461832ad9 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -7,30 +7,16 @@

 (function ($, Drupal) {
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach: function attach(context, settings) {
+    attach: function attach(context) {
       var $context = $(context);
       var elements = void 0;

-      function initFileValidation(selector) {
-        $context.find(selector).once('fileValidate').on('change.fileValidate', { extensions: elements[selector] }, Drupal.file.validateExtension);
-      }
-
-      if (settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(initFileValidation);
-      }
+      $context.find('[data-drupal-validate-extensions]').once('fileValidate').on('change.fileValidate', Drupal.file.validateExtension);
     },
     detach: function detach(context, settings, trigger) {
       var $context = $(context);
-      var elements = void 0;
-
-      function removeFileValidation(selector) {
-        $context.find(selector).removeOnce('fileValidate').off('change.fileValidate', Drupal.file.validateExtension);
-      }
-
-      if (trigger === 'unload' && settings.file && settings.file.elements) {
-        elements = settings.file.elements;
-        Object.keys(elements).forEach(removeFileValidation);
+      if (trigger === 'unload') {
+        $context.find('[data-drupal-validate-extensions]').removeOnce('fileValidate').off('change.fileValidate', Drupal.file.validateExtension);
       }
     }
   };
@@ -76,15 +62,22 @@

       $('.file-upload-js-error').remove();

-      var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
-      if (extensionPattern.length > 1 && this.value.length > 0) {
-        var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
-        if (!acceptableMatch.test(this.value)) {
-          var error = Drupal.t('The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.', {
-            '%filename': this.value.replace('C:\\fakepath\\', ''),
-            '%extensions': extensionPattern.replace(/\|/g, ', ')
-          });
-          $(this).closest('div.js-form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
+      var validExtensions = JSON.parse(event.target.getAttribute('data-drupal-validate-extensions'));
+      if (validExtensions.length > 1 && event.target.value.length > 0) {
+        var acceptableMatch = new RegExp('\\.(' + validExtensions.join('|') + ')$', 'i');
+        var testSubjects = event.target.files;
+        if (!testSubjects) {
+          testSubjects = [{ name: event.target.value }];
+        }
+        var invalidFiles = [];
+        for (var i = 0; i < testSubjects.length; i++) {
+          var testSubject = testSubjects[i];
+          if (!acceptableMatch.test(testSubject.name)) {
+            invalidFiles.push(testSubject.name.replace('C:\\fakepath\\', ''));
+          }
+        }
+        if (invalidFiles.length) {
+          $(this).closest('div.js-form-managed-file').prepend(Drupal.theme('fileValidationError', invalidFiles, validExtensions));
           this.value = '';

           event.stopImmediatePropagation();
@@ -133,4 +126,23 @@
       window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
     }
   };
-})(jQuery, Drupal);
\ No newline at end of file
+
+  Drupal.theme.fileValidationError = function (invalidFiles, validExtensions) {
+    var error = Drupal.formatPlural(invalidFiles.length, 'The selected file %filename cannot be uploaded.', '@count of the selected files cannot be uploaded.', {
+      '%filename': invalidFiles[0]
+    });
+    if (invalidFiles.length > 1) {
+      error += '<ul>';
+      invalidFiles.forEach(function (file) {
+        error += '<li>' + Drupal.formatString('%filename', { '%filename': file }) + '</li>';
+      });
+      error += '</ul>';
+    }
+    error += ' ';
+    error += Drupal.t('Only files with the following extensions are allowed: %extensions.', {
+      '%extensions': validExtensions.join(', ')
+    });
+
+    return '<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>';
+  };
+})(jQuery, Drupal);
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 95798c673..923fc60ac 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -2,6 +2,7 @@

 namespace Drupal\file\Element;

+use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
@@ -350,10 +351,9 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
       }
     }

-    // Add the extension list to the page as JavaScript settings.
     if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
-      $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
-      $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list;
+      $extension_list = array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]));
+      $element['upload']['#attributes']['data-drupal-validate-extensions'] = Json::encode($extension_list);
     }

     // Let #id point to the file element, so the field label's 'for' corresponds
diff --git a/core/modules/file/src/Tests/FileFieldWidgetTest.php b/core/modules/file/src/Tests/FileFieldWidgetTest.php
index 3102bd744..971f4ba39 100644
--- a/core/modules/file/src/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/src/Tests/FileFieldWidgetTest.php
@@ -427,6 +427,9 @@ public function testWidgetValidation() {
       $test_file_image = $this->getTestFile('image');
       $name = 'files[' . $field_name . '_0]';

+      // Check field is marked with expected attr for client-side validation.
+      $this->assertFieldByXPath('//input[@type="file" and @data-drupal-validate-extensions=\'["txt"]\']', NULL, 'data-drupal-validate-extensions is present with expected value.');
+
       // Upload file with incorrect extension, check for validation error.
       $edit[$name] = \Drupal::service('file_system')->realpath($test_file_image->getFileUri());
       switch ($type) {
