diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
index 07bd415..bf2310a 100644
--- a/core/lib/Drupal/Core/Render/Element/File.php
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -44,7 +44,7 @@ public function getInfo() {
    */
   public static function processFile(&$element, FormStateInterface $form_state, &$complete_form) {
     if ($element['#multiple']) {
-      $element['#attributes'] = ['multiple' => 'multiple'];
+      $element['#attributes']['multiple'] = 'multiple';
       $element['#name'] .= '[]';
     }
     return $element;
diff --git a/core/modules/file/file.es6.js b/core/modules/file/file.es6.js
index 8ed377e..7e7a6ca 100644
--- a/core/modules/file/file.es6.js
+++ b/core/modules/file/file.es6.js
@@ -22,35 +22,22 @@
    *   Detaches validation for file extensions.
    */
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach: function (context, settings) {
+    attach: function (context) {
       var $context = $(context);
       var 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: function (context, settings, trigger) {
       var $context = $(context);
-      var 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);
-      }
     }
   };
 
@@ -138,11 +125,17 @@
       $('.file-upload-js-error').remove();
 
       // Add client side validation for the input[type=file].
-      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.', {
+      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
@@ -150,10 +143,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();
@@ -254,4 +248,34 @@
     }
   };
 
+  /**
+   * 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 27e2775..ba69238 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -10,30 +10,17 @@
   'use strict';
 
   Drupal.behaviors.fileValidateAutoAttach = {
-    attach: function attach(context, settings) {
+    attach: function attach(context) {
       var $context = $(context);
       var 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: function detach(context, settings, trigger) {
       var $context = $(context);
-      var elements;
-
-      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);
       }
     }
   };
@@ -77,15 +64,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();
@@ -140,4 +134,23 @@
       window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
     }
   };
+
+  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);
\ No newline at end of file
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 45c8aa7..1339438 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;
@@ -342,10 +343,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 ddfd8f4..e6cb08f 100644
--- a/core/modules/file/src/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/src/Tests/FileFieldWidgetTest.php
@@ -428,6 +428,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_realpath($test_file_image->getFileUri());
       switch ($type) {
diff --git a/core/modules/file/src/Tests/FileManagedFileElementTest.php b/core/modules/file/src/Tests/FileManagedFileElementTest.php
index 8abff25..b102e47 100644
--- a/core/modules/file/src/Tests/FileManagedFileElementTest.php
+++ b/core/modules/file/src/Tests/FileManagedFileElementTest.php
@@ -18,6 +18,10 @@ public function testManagedFile() {
     $this->drupalGet('file/test');
     $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.');
 
+    // Check that relevant data- attributes are present for validations
+    $this->drupalGet('file/test/validation/extension');
+    $this->assertFieldByXPath('//input[@name="files[file]" and @data-drupal-validate-extensions=\'["txt"]\']', NULL, 'The data-drupal-validate-extensions attribute is present with the expected value on the upload element.');
+
     // Perform the tests with all permutations of $form['#tree'],
     // $element['#extended'], and $element['#multiple'].
     $test_file = $this->getTestFile('text');
diff --git a/core/modules/file/tests/file_module_test/file_module_test.routing.yml b/core/modules/file/tests/file_module_test/file_module_test.routing.yml
index 15f7d0f..0c85f6e 100644
--- a/core/modules/file/tests/file_module_test/file_module_test.routing.yml
+++ b/core/modules/file/tests/file_module_test/file_module_test.routing.yml
@@ -8,3 +8,11 @@ file_module_test.managed_test:
     default_fids: NULL
   requirements:
     _access: 'TRUE'
+
+file_module_test.managed_validations_test:
+  path: '/file/test/validation/{validation_type}'
+  defaults:
+    _form: '\Drupal\file_module_test\Form\FileModuleTestValidationForm'
+    validation_type: 'extension'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/file/tests/file_module_test/src/Form/FileModuleTestValidationForm.php b/core/modules/file/tests/file_module_test/src/Form/FileModuleTestValidationForm.php
new file mode 100644
index 0000000..e6a0306
--- /dev/null
+++ b/core/modules/file/tests/file_module_test/src/Form/FileModuleTestValidationForm.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\file_module_test\Form\FileModuleTestForm.
+ */
+
+namespace Drupal\file_module_test\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form controller for file_module_test module.
+ */
+class FileModuleTestValidationForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'file_module_test_validation_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param string $validation_type
+   *   The validation being tested, ie 'extension', 'size'
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $validation_type = 'extension') {
+    switch ($validation_type) {
+      case 'extension':
+        $validator = ['file_validate_extensions' => ['txt']];
+        break;
+      case 'size':
+        $validator = ['file_validate_size' => [1024]];
+        break;
+      default:
+        return FALSE;
+    }
+
+    $form['file'] = [
+      '#type' => 'managed_file',
+      '#title' => $this->t('Managed File'),
+      '#upload_location' => 'public://test',
+      '#progress_message' => $this->t('Please wait...'),
+      '#upload_validators' => $validator,
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    drupal_set_message($this->t('Test submission handler was allowed to execute.'));
+  }
+
+}
diff --git a/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php b/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php
new file mode 100644
index 0000000..edc0633
--- /dev/null
+++ b/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Tests\file\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
+
+
+/**
+ * Tests the client side JavaScript validation.
+ *
+ * @group file
+ */
+class ClientValidationTest extends JavascriptTestBase {
+
+  use ImageFieldCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node', 'image', 'field_ui'];
+
+
+  /**
+   * The real path to the test file.
+   *
+   * @var string
+   */
+  protected $file_path;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create the Article node type.
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+
+    // Create a field with a basic filetype restriction.
+    $field_name = strtolower($this->randomMachineName());
+    $field_settings = [
+      'file_extensions' => 'png',
+    ];
+    $formatter_settings = [
+      'image_style' => 'large',
+      'image_link' => '',
+    ];
+    $this->createImageField($field_name, 'article', [], $field_settings, [], $formatter_settings);
+
+    // Log in as a content author who can create Articles.
+    $user = $this->drupalCreateUser([
+      'access content',
+      'create article content',
+      'edit any article content',
+      'delete any article content',
+    ]);
+    $this->drupalLogin($user);
+
+    $uri = 'public://file.txt';
+    file_unmanaged_save_data('Drupal rocks!', $uri, FILE_EXISTS_REPLACE);
+    $this->file_path = \Drupal::service('file_system')->realpath($uri);
+  }
+
+  /**
+   * Tests the client side validation on file fields.
+   */
+  public function testDisallowedExtensionErrorMessage() {
+    $this->drupalGet('node/add/article');
+    $assert = $this->assertSession();
+
+    // Test uploading a file with a file extension not allowed.
+    // Try to upload a file with an unallowed extension
+    $input_selector = 'input.form-file';
+    $input = $assert->elementExists('css', $input_selector);
+    $input->attachFile($this->file_path);
+
+    // Trigger the upload logic with a mock "drop" event.
+    $script = 'var e = jQuery.Event("drop");'
+      . 'e.originalEvent = {dataTransfer: {files: jQuery("input.form-file").get(0).files}};'
+      . 'e.preventDefault = e.stopPropagation = function () {};'
+      . 'jQuery("input.form-file").trigger(e);';
+    $this->getSession()->executeScript($script);
+
+    // Verify that the error message is there.
+    $error_selector = '.file-upload-js-error';
+    // Verify that there is one and only one error message on the page.
+    $assert->waitForElement('css', $error_selector);
+    $condition = "jQuery('$error_selector').length == 1";
+    $this->assertJsCondition($condition);
+    $assert->elementExists('css', $error_selector);
+  }
+
+}
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index 9c105c1..9b2f873 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -325,8 +325,13 @@ public function testImageFieldSettings() {
       'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri),
     ];
     $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_2_upload_button');
-    $this->assertNoRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-2-upload" name="files[' . $field_name . '_2][]" size="22" class="js-form-file form-file">');
-    $this->assertRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-3-upload" name="files[' . $field_name . '_3][]" size="22" class="js-form-file form-file">');
+    $this->assertNoFieldByXPath('//input[@id="edit-' . strtr($field_name, '_', '-') . '-2-upload"]', 'Upload field for second file not present after it is uploaded.');
+
+    $this->assertTrue($this->cssSelect('input#edit-' . strtr($field_name, '_', '-') . '-3-upload[multiple=multiple]'), 'Third file field presented having multiple attribute');
+    $this->assertFieldByName('files[' . $field_name . '_3][]', NULL, 'Third file field has correct name');
+    $this->assertTrue($this->cssSelect('input[id=edit-' . strtr($field_name, '_', '-') . '-3-upload][size=22]'), 'Third file field presented having correct size attribute');
+    $this->assertTrue($this->cssSelect('input.js-form-file.form-file[id=edit-' . strtr($field_name, '_', '-') . '-3-upload]'), 'Third file field presented having correct css classes');
+    $this->assertFieldByXPath('//input[@id="edit-' . strtr($field_name, '_', '-') . '-3-upload"] and @data-drupal-validate-extensions=\'["txt"]\']', NULL, 'The data-drupal-validate-extensions attribute is present with the expected value on the upload element.');
   }
 
   /**
