diff --git a/core/misc/icons/bebebe/dropzone-new.svg b/core/misc/icons/bebebe/dropzone-new.svg
new file mode 100644
index 0000000000..b3b8e494af
--- /dev/null
+++ b/core/misc/icons/bebebe/dropzone-new.svg
@@ -0,0 +1,13 @@
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="100" width="500" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<g transform="translate(-95.714287,-390.93359)">
+<g fill="#7fc9f7" transform="matrix(1.840195,0,0,1.840195,331.1186,426.62055)">
+<path fill="#7fc9f7" d="m12.5,7h-5c-0.274,0-0.5-0.225-0.5-0.5v-5c0-0.275-0.225-0.5-0.5-0.5h-3c-0.275,0-0.5,0.225-0.5,0.5v12.03c0,0.275,0.225,0.5,0.5,0.5h9.002c0.275,0,0.5-0.225,0.5-0.5v-6.03c0-0.275-0.225-0.5-0.5-0.5zm-4-1h4c0.275,0,0.34-0.159,0.146-0.354l-4.295-4.292c-0.195-0.195-0.353-0.129-0.353,0.146v4c0,0.275,0.225,0.5,0.5,0.5z"/>
+</g>
+<rect stroke-linejoin="bevel" stroke-dasharray="9.26, 9.26" stroke-dashoffset="30.87" ry="4.464" height="63.86" width="66.36" stroke="#bebebe" stroke-linecap="round" stroke-miterlimit="4" y="408.9" x="110.7" stroke-width="3.087" fill="none"/>
+<rect stroke-linejoin="bevel" stroke-dasharray="9.26, 9.26" stroke-dashoffset="30.87" ry="4.464" height="63.86" width="66.36" stroke="#6dcbff" stroke-linecap="round" stroke-miterlimit="4" y="408.9" x="210.7" stroke-width="3.087" fill="none"/>
+<path fill="#6dcbff" d="m241.7,428.8,0,9.031-9.031,0,0,6.031,9.031,0,0,9,6,0,0-9,9.031,0,0-6.031-9.031,0,0-9.031-6,0z"/>
+<g fill="#bfbfbd" stroke-opacity="0" transform="matrix(1.840195,0,0,1.840195,131.1186,426.62055)">
+<path fill="#bebebe" stroke-opacity="0" d="m12.5,7h-5c-0.274,0-0.5-0.225-0.5-0.5v-5c0-0.275-0.225-0.5-0.5-0.5h-3c-0.275,0-0.5,0.225-0.5,0.5v12.03c0,0.275,0.225,0.5,0.5,0.5h9.002c0.275,0,0.5-0.225,0.5-0.5v-6.03c0-0.275-0.225-0.5-0.5-0.5zm-4-1h4c0.275,0,0.34-0.159,0.146-0.354l-4.295-4.292c-0.195-0.195-0.353-0.129-0.353,0.146v4c0,0.275,0.225,0.5,0.5,0.5z"/>
+</g>
+</g>
+</svg>
diff --git a/core/misc/icons/ee0000/ex.svg b/core/misc/icons/ee0000/ex.svg
new file mode 100644
index 0000000000..6b45a1d572
--- /dev/null
+++ b/core/misc/icons/ee0000/ex.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#ee0000" d="M3.51 13.925c.194.194.512.195.706.001l3.432-3.431c.194-.194.514-.194.708 0l3.432 3.431c.192.194.514.193.707-.001l1.405-1.417c.191-.195.189-.514-.002-.709l-3.397-3.4c-.192-.193-.192-.514-.002-.708l3.401-3.43c.189-.195.189-.515 0-.709l-1.407-1.418c-.195-.195-.513-.195-.707-.001l-3.43 3.431c-.195.194-.516.194-.708 0l-3.432-3.431c-.195-.195-.512-.194-.706.001l-1.407 1.417c-.194.195-.194.515 0 .71l3.403 3.429c.193.195.193.514-.001.708l-3.4 3.399c-.194.195-.195.516-.001.709l1.406 1.419z"/></svg>
diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc
index a3b8868cd0..47291bdb95 100644
--- a/core/modules/file/file.field.inc
+++ b/core/modules/file/file.field.inc
@@ -51,6 +51,9 @@ function template_preprocess_file_widget_multiple(&$variables) {
     if (empty($widget['#files'])) {
       $widget['#title'] = $element['#file_upload_title'];
       $widget['#description'] = \Drupal::service('renderer')->renderPlain($element['#file_upload_description']);
+      if (!empty($widget['#default_image']['rendered'])) {
+        $widget['#description'] .= $widget['#default_image']['rendered'];
+      }
       continue;
     }
 
@@ -149,6 +152,12 @@ function template_preprocess_file_upload_help(&$variables) {
   if (!empty($description)) {
     $descriptions[] = FieldFilteredMarkup::create($description);
   }
+  if (isset($upload_validators['file_validate_extensions'])) {
+    $descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['file_validate_extensions'][0]]);
+  }
+  if (isset($upload_validators['file_validate_size'])) {
+    $descriptions[] = t('Up to @size.', ['@size' => format_size($upload_validators['file_validate_size'][0])]);
+  }
   if (isset($cardinality)) {
     if ($cardinality == -1) {
       $descriptions[] = t('Unlimited number of files can be uploaded to this field.');
@@ -157,12 +166,6 @@ function template_preprocess_file_upload_help(&$variables) {
       $descriptions[] = \Drupal::translation()->formatPlural($cardinality, 'One file only.', 'Maximum @count files.');
     }
   }
-  if (isset($upload_validators['file_validate_size'])) {
-    $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]);
-  }
-  if (isset($upload_validators['file_validate_extensions'])) {
-    $descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['file_validate_extensions'][0]]);
-  }
 
   if (isset($upload_validators['file_validate_image_resolution'])) {
     $max = $upload_validators['file_validate_image_resolution'][0];
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index f1da5a4b5f..4491594234 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -176,6 +176,7 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
       $elements['#theme_wrappers'] = ['details'];
       $elements['#process'] = [[get_class($this), 'processMultiple']];
       $elements['#title'] = $title;
+      $elements['#required'] = $this->fieldDefinition->isRequired();
 
       $elements['#description'] = $description;
       $elements['#field_name'] = $field_name;
diff --git a/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php b/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php
index 5ef877b4d0..b3be5b6fe2 100644
--- a/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php
+++ b/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php
@@ -41,50 +41,82 @@ public function testManagedFile() {
     // $element['#extended'], and $element['#multiple'].
     $filename = \Drupal::service('file_system')->tempnam('temporary://', "testManagedFile") . '.txt';
     file_put_contents($filename, $this->randomString(128));
-    foreach ([0, 1] as $tree) {
-      foreach ([0, 1] as $extended) {
-        foreach ([0, 1] as $multiple) {
-          $path = 'file/test/' . $tree . '/' . $extended . '/' . $multiple;
-          $input_base_name = $tree ? 'nested_file' : 'file';
-          $file_field_name = $multiple ? 'files[' . $input_base_name . '][]' : 'files[' . $input_base_name . ']';
+    foreach (['stable', 'seven'] as $themename) {
+      \Drupal::service('theme_installer')->install([$themename]);
+      $theme_config = \Drupal::configFactory()->getEditable('system.theme');
+      $theme_config->set('default', $themename)->save();
+      foreach ([0, 1] as $tree) {
+        foreach ([0, 1] as $extended) {
+          foreach ([0, 1] as $multiple) {
+            $path = 'file/test/' . $tree . '/' . $extended . '/' . $multiple;
+            $input_base_name = $tree ? 'nested_file' : 'file';
+            $file_field_name = $multiple ? 'files[' . $input_base_name . '][]' : 'files[' . $input_base_name . ']';
 
-          // Now, test the Upload and Remove buttons, with Ajax.
-          // Upload, then Submit.
-          $last_fid_prior = $this->getLastFileId();
-          $this->drupalGet($path);
-          $this->getSession()->getPage()->attachFileToField($file_field_name, $this->container->get('file_system')->realpath($filename));
-          $uploaded_file = $this->assertSession()->waitForElement('css', '.file--mime-text-plain');
-          $this->assertNotEmpty($uploaded_file);
-          $last_fid = $this->getLastFileId();
-          $this->assertGreaterThan($last_fid_prior, $last_fid, 'New file got uploaded.');
-          $this->drupalPostForm(NULL, [], t('Save'));
+            // Now, test the Upload and Remove buttons, with Ajax.
+            // Upload, then Submit.
+            $last_fid_prior = $this->getLastFileId();
+            $this->drupalGet($path);
 
-          // Remove, then Submit.
-          $remove_button_title = $multiple ? t('Remove selected') : t('Remove');
-          $this->drupalGet($path . '/' . $last_fid);
-          if ($multiple) {
-            $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
-            $this->getSession()->getPage()->checkField($selected_checkbox);
-          }
-          $this->getSession()->getPage()->pressButton($remove_button_title);
-          $this->assertSession()->assertWaitOnAjaxRequest();
-          $this->drupalPostForm(NULL, [], t('Save'));
-          $this->assertSession()->responseContains(t('The file ids are %fids.', ['%fids' => '']));
+            // Assert the dropzone enhancements in the seven theme.
+            if ($themename === 'seven') {
+              $trigger = $this->assertSession()->elementExists('css', '.dropzone .dropzone__trigger');
+              $trigger->mouseOver();
+              $this->assertEquals(TRUE, $trigger->hasClass('is-hovering'));
+              $this->assertSession()->elementExists('css', '.dropzone .dropzone__no-trigger .form-managed-file-wrapper--dropzone .js-dropzone-add-button');
+            }
+            else {
+              $this->assertSession()->elementNotExists('css', '.dropzone');
+            }
 
-          // Upload, then Remove, then Submit.
-          $this->drupalGet($path);
-          $this->getSession()->getPage()->attachFileToField($file_field_name, $this->container->get('file_system')->realpath($filename));
-          $uploaded_file = $this->assertSession()->waitForElement('css', '.file--mime-text-plain');
-          $this->assertNotEmpty($uploaded_file);
-          if ($multiple) {
-            $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
-            $this->getSession()->getPage()->checkField($selected_checkbox);
-          }
-          $this->getSession()->getPage()->pressButton($remove_button_title);
-          $this->assertSession()->assertWaitOnAjaxRequest();
+            $this->getSession()
+              ->getPage()
+              ->attachFileToField($file_field_name, $this->container->get('file_system')
+                ->realpath($filename));
+            $uploaded_file = $this->assertSession()
+              ->waitForElement('css', '.file--mime-text-plain');
+            $this->assertNotEmpty($uploaded_file);
+            $last_fid = $this->getLastFileId();
+            $this->assertGreaterThan($last_fid_prior, $last_fid, 'New file got uploaded.');
+            if ($multiple && $themename === 'seven') {
+              $files_wrapper = $this->assertSession()->elementExists('css', '.form-managed-file-items');
+              $this->assertSession()->buttonExists('Remove selected', $files_wrapper);
+              $this->assertSession()->elementExists('css', '.form-type-checkbox');
+            }
+            $this->drupalPostForm(NULL, [], t('Save'));
+
+            // Remove, then Submit.
+            $remove_button_title = $multiple ? t('Remove selected') : t('Remove');
+            $this->drupalGet($path . '/' . $last_fid);
+            if ($multiple) {
+              $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
+              $this->getSession()->getPage()->checkField($selected_checkbox);
+            }
+            $this->getSession()->getPage()->pressButton($remove_button_title);
+            $this->assertSession()->assertWaitOnAjaxRequest();
+            $this->drupalPostForm(NULL, [], t('Save'));
+            $this->assertSession()
+              ->responseContains(t('The file ids are %fids.', ['%fids' => '']));
 
-          $this->drupalPostForm(NULL, [], t('Save'));
-          $this->assertSession()->responseContains(t('The file ids are %fids.', ['%fids' => '']));
+            // Upload, then Remove, then Submit.
+            $this->drupalGet($path);
+            $this->getSession()
+              ->getPage()
+              ->attachFileToField($file_field_name, $this->container->get('file_system')
+                ->realpath($filename));
+            $uploaded_file = $this->assertSession()
+              ->waitForElement('css', '.file--mime-text-plain');
+            $this->assertNotEmpty($uploaded_file);
+            if ($multiple) {
+              $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
+              $this->getSession()->getPage()->checkField($selected_checkbox);
+            }
+            $this->getSession()->getPage()->pressButton($remove_button_title);
+            $this->assertSession()->assertWaitOnAjaxRequest();
+
+            $this->drupalPostForm(NULL, [], t('Save'));
+            $this->assertSession()
+              ->responseContains(t('The file ids are %fids.', ['%fids' => '']));
+          }
         }
       }
     }
diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
index d7556b4ed6..e30f330b65 100644
--- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
+++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php
@@ -266,7 +266,7 @@ public static function process($element, FormStateInterface $form_state, $form)
       '#description' => t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
       // @see https://www.drupal.org/node/465106#alt-text
       '#maxlength' => 512,
-      '#weight' => -12,
+      '#weight' => 1,
       '#access' => (bool) $item['fids'] && $element['#alt_field'],
       '#required' => $element['#alt_field_required'],
       '#element_validate' => $element['#alt_field_required'] == 1 ? [[get_called_class(), 'validateRequiredFields']] : [],
@@ -277,7 +277,7 @@ public static function process($element, FormStateInterface $form_state, $form)
       '#default_value' => isset($item['title']) ? $item['title'] : '',
       '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
       '#maxlength' => 1024,
-      '#weight' => -11,
+      '#weight' => 2,
       '#access' => (bool) $item['fids'] && $element['#title_field'],
       '#required' => $element['#title_field_required'],
       '#element_validate' => $element['#title_field_required'] == 1 ? [[get_called_class(), 'validateRequiredFields']] : [],
diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
index 4014fc8378..ca42821a96 100644
--- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
+++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
@@ -254,7 +254,7 @@ public function testImageFieldSettings() {
     $this->assertFieldByName('settings[min_resolution][y]', '10', 'Expected min resolution Y value of 10.');
 
     $this->drupalGet('node/add/article');
-    $this->assertText(t('50 KB limit.'), 'Image widget max file size is displayed on article form.');
+    $this->assertText(t('Up to 50 KB.'), 'Image widget max file size is displayed on article form.');
     $this->assertText(t('Allowed types: @extensions.', ['@extensions' => $test_image_extension]), 'Image widget allowed file types displayed on article form.');
     $this->assertText(t('Images must be larger than 10x10 pixels. Images larger than 100x100 pixels will be resized.'), 'Image widget allowed resolution displayed on article form.');
 
diff --git a/core/themes/seven/css/components/dropzone.css b/core/themes/seven/css/components/dropzone.css
new file mode 100644
index 0000000000..eca8d7c401
--- /dev/null
+++ b/core/themes/seven/css/components/dropzone.css
@@ -0,0 +1,230 @@
+/**
+ * Dropzone.
+ */
+.dropzone {
+  position: relative;
+  display: flex;
+  width: 100%;
+  height: 100%;
+  border: 1px solid #bfbfbf;
+  border-radius: 2px;
+  background: #fcfcfa;
+}
+
+/* Dropzone trigger area. */
+.dropzone__trigger {
+  flex: 0 0 100px;
+  min-height: 100px;
+  border-right: 1px solid #bfbfbf;
+  background: url("../../../../misc/icons/bebebe/dropzone-new.svg") 0 center no-repeat;
+}
+[dir="rtl"] .dropzone__trigger {
+  border-right: none;
+  border-left: 1px solid #bfbfbf;
+}
+
+.dropzone__trigger.is-hovering {
+  background-position: -100px center;
+}
+
+.dropzone__trigger.is-hidden {
+  background-image: none;
+}
+
+/* Dropzone no-trigger area. */
+.dropzone__no-trigger .form-managed-file-wrapper {
+  flex-grow: 1;
+}
+
+.dropzone__no-trigger .managed-file-upload {
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: inline-block;
+  width: 100px;
+  height: 100%;
+  padding: 0;
+  cursor: pointer;
+  opacity: 0;
+}
+[dir="rtl"] .dropzone__no-trigger .managed-file-upload {
+  right: 0;
+  left: auto;
+}
+
+.dropzone__no-trigger .js-dropzone-add-button {
+  margin: 20px 10px 5px 10px;
+}
+
+.dropzone__no-trigger .js-dropzone-add-button:before {
+  margin-left: -0.2em;
+  content: "+";
+  font-weight: 900;
+}
+
+.dropzone__no-trigger .description {
+  display: table-cell;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  max-width: 500px;
+  padding: 0 10px 20px 10px;
+}
+
+.dropzone__no-trigger .description li {
+  display: inline-block;
+}
+
+.dropzone__no-trigger .description li:after {
+  padding: 0 5px;
+  content: "\00b7";
+}
+
+.dropzone__no-trigger .description li:last-child:after {
+  content: none;
+}
+
+.dropzone__no-trigger .messages {
+  margin: 10px 10px 0 18px;
+}
+
+/**
+ * Position the default image.
+ */
+.dropzone__no-trigger .form-managed-file.image-widget {
+  display: block;
+  padding: 0 10px 10px 10px;
+}
+
+.dropzone__no-trigger .form-managed-file .image-preview {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  padding: 0;
+}
+
+@media screen and (min-width: 48em) {
+  .dropzone__no-trigger {
+    display: flex;
+    flex-grow: 1;
+  }
+
+  .dropzone__no-trigger .form-managed-file.image-widget {
+    box-sizing: border-box;
+    width: 100px;
+    margin: 0;
+    padding: 10px;
+  }
+}
+
+/**
+ * Upload buttons AJAX throbber.
+ */
+.dropzone .upload-button + .ajax-progress {
+  position: absolute;
+  top: 50%;
+  left: 50px;
+  margin: -10px -10px;
+}
+[dir="rtl"] .dropzone .upload-button + .ajax-progress {
+  right: 50px;
+  left: auto;
+}
+
+.dropzone .upload-button + .ajax-progress .message {
+  color: transparent;
+  font-size: 0;
+}
+
+/**
+ * Single file/image widget after upload.
+ */
+.form-managed-file-wrapper--dropzone.has-file {
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #bfbfbf;
+  border-radius: 2px;
+  background: #fcfcfa;
+}
+
+.form-managed-file-wrapper--dropzone.has-file:before {
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: block;
+  width: 100px;
+  height: 100%;
+  min-height: 45px;
+  content: "";
+  border-right: 1px solid #bfbfbf;
+  background: url("../../../../misc/icons/bebebe/dropzone-new.svg") -200px center no-repeat;
+}
+[dir="rtl"] .form-managed-file-wrapper--dropzone.has-file:before {
+  right: 0;
+  left: auto;
+  border-right: none;
+  border-left: 1px solid #bfbfbf;
+}
+
+.field--type-image .form-managed-file-wrapper--dropzone.has-file:before {
+  background: none;
+}
+
+.form-managed-file-wrapper--dropzone.has-file > .form-managed-file,
+.form-managed-file-wrapper--dropzone.has-file > .description {
+  margin-left: 100px;
+  padding: 1em;
+}
+[dir="rtl"] .form-managed-file-wrapper--dropzone.has-file > .form-managed-file,
+[dir="rtl"] .form-managed-file-wrapper--dropzone.has-file > .description {
+  margin-right: 100px;
+  margin-left: 0;
+}
+
+.form-managed-file-wrapper--dropzone.has-file > .description {
+  padding-top: 0;
+}
+
+.form-managed-file-wrapper--dropzone.has-file > .form-managed-file.image-widget {
+  margin-left: 0;
+  padding: 0;
+}
+[dir="rtl"] .form-managed-file-wrapper--dropzone.has-file > .form-managed-file.image-widget {
+  margin-right: 0;
+}
+
+.form-managed-file-wrapper--dropzone.has-file .image-preview {
+  padding: 10px;
+}
+
+.form-managed-file-wrapper--dropzone.has-file .image-widget-data {
+  padding: 1em;
+}
+
+/**
+ * Multi value managed_file element.
+ */
+.form-managed-file-items {
+  margin-bottom: 10px;
+  padding: 10px 15px;
+  border: 1px solid #bfbfbf;
+  border-radius: 2px;
+  background: #fcfcfa;
+}
+
+.form-managed-file-items .form-item {
+  margin: 10px 0;
+}
+
+.form-managed-file-items .form-checkbox {
+  margin-right: 5px;
+}
+[dir="rtl"] .form-managed-file-items .form-checkbox {
+  margin-right: 0;
+  margin-left: 5px;
+}
+
+/* Fix cursor on file input button in chrome. */
+.dropzone__no-trigger .managed-file-upload::-webkit-file-upload-button {
+  cursor: pointer;
+}
diff --git a/core/themes/seven/css/components/managed-file.css b/core/themes/seven/css/components/managed-file.css
new file mode 100644
index 0000000000..458880c682
--- /dev/null
+++ b/core/themes/seven/css/components/managed-file.css
@@ -0,0 +1,166 @@
+/**
+ * Image widget.
+ */
+
+.form-type-managed-file {
+  position: relative;
+}
+
+.image-widget {
+  display: table;
+}
+
+.image-preview,
+.image-widget-data {
+  display: table-cell;
+}
+
+.image-preview {
+  width: 80px;
+  padding-right: 10px;
+  vertical-align: middle;
+}
+[dir="rtl"] .image-preview {
+  padding-right: 0;
+  padding-left: 10px;
+}
+
+.image-preview img {
+  width: 80px;
+  height: auto;
+  border: 0;
+}
+
+.file-upload-help {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.image-widget-data {
+  vertical-align: top;
+}
+
+/**
+ * Remove button.
+ */
+.remove-button-multiple {
+  display: block;
+  margin: 20px 0 10px 0;
+}
+
+.form-type-managed-file .remove-button {
+  position: absolute;
+  top: 15px;
+  right: 10px;
+  width: auto;
+  margin: 0;
+  padding: 0 20px 0 0;
+  text-transform: lowercase;
+  color: transparent;
+  border: 0;
+  background: transparent url(../../../../misc/icons/787878/ex.svg) right 0 no-repeat;
+  box-shadow: none;
+  font-weight: normal;
+  line-height: 16px;
+}
+[dir="rtl"] .form-type-managed-file .remove-button {
+  right: auto;
+  left: 10px;
+  background-position: left 0;
+}
+
+.form-type-managed-file .remove-button:focus {
+  outline: none;
+  box-shadow: none;
+}
+
+.form-type-managed-file .remove-button:hover {
+  color: #e00;
+  border: 0;
+  background: url(../../../../misc/icons/ee0000/ex.svg) right 0 no-repeat;
+  box-shadow: none;
+}
+[dir="rtl"] .form-type-managed-file .remove-button:hover {
+  background-position: left 0;
+}
+
+/**
+ * Remove buttons ajax throbber.
+ */
+.form-type-managed-file .remove-button + .ajax-progress {
+  position: absolute;
+  top: 8px;
+  right: 9px;
+  padding: 2px;
+  background: #fcfcfa;
+}
+[dir="rtl"] .form-type-managed-file .remove-button + .ajax-progress {
+  right: auto;
+  left: 5px;
+}
+
+/**
+ * Multi uploads table.
+ */
+.form-type-managed-file table {
+  width: 100%;
+  margin: 10px 0 25px 0;
+}
+
+.form-type-managed-file td {
+  vertical-align: top;
+}
+
+.form-type-managed-file .draggable td:first-child {
+  display: table;
+}
+
+.form-type-managed-file .draggable td:first-child a.tabledrag-handle,
+.form-type-managed-file .draggable td:first-child .form-managed-file {
+  display: table-cell;
+  padding-top: 10px;
+  vertical-align: top;
+}
+
+.form-type-managed-file .draggable td:first-child a.tabledrag-handle {
+  float: none;
+}
+
+.form-type-managed-file th:last-child,
+.form-type-managed-file td:last-child {
+  position: relative;
+  text-align: right;
+  white-space: nowrap;
+}
+
+.form-type-managed-file th:last-child {
+  font-size: 0;
+}
+
+.form-type-managed-file td:last-child {
+  padding: 10px;
+}
+
+.form-type-managed-file table .image-widget .image-preview {
+  vertical-align: top;
+}
+
+.form-type-managed-file table .remove-button {
+  position: static;
+}
+
+/**
+ * Stop filenames from breaking out of their container.
+ */
+.form-type-managed-file .file {
+  word-break: break-word;
+}
+
+/**
+ * Stop input elements from breaking out of their container.
+ */
+.form-type-managed-file input.form-text {
+  width: 100%;
+  max-width: 500px;
+}
diff --git a/core/themes/seven/js/dropzone.es6.js b/core/themes/seven/js/dropzone.es6.js
new file mode 100644
index 0000000000..16e87fd4d6
--- /dev/null
+++ b/core/themes/seven/js/dropzone.es6.js
@@ -0,0 +1,151 @@
+(function($, Drupal, drupalSettings) {
+  /**
+   * Initializes a dropzone for managed file fields.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches a dropzone to managed file fields.
+   */
+  Drupal.behaviors.dropzone = {
+    attach(context) {
+      // Add 'dropzone-enabled' class to managed file fields.
+      $(context)
+        .find('.js-form-managed-file-wrapper')
+        .addClass('form-managed-file-wrapper--dropzone')
+        .each(function() {
+          // Move the image description for theming purposes.
+          if ($(this).hasClass('has-file')) {
+            $(this)
+              .find('> .js-description')
+              .appendTo($('.js-image-widget-data', this));
+          }
+
+          // Move added files in managed_file elements that allow multiple
+          // values for theming purposes.
+          const $files = $(this).find('.js-form-type-checkbox');
+          if ($files.length) {
+            const $filesWrapper = $(
+              '<div class="form-managed-file-items"></div>',
+            );
+            $files.appendTo($filesWrapper);
+            $filesWrapper.insertBefore($(this));
+            $(this)
+              .find('.js-remove-button-multiple')
+              .appendTo($filesWrapper);
+          }
+        });
+
+      // Add dropzone HTML to managed file fields.
+      $(context)
+        .find('.js-form-file')
+        .once('managed-file-dropzone')
+        .each(function() {
+          const $wrapper = $(this).parents('.js-form-managed-file-wrapper');
+
+          // Create extra button and add to wrapper.
+          let buttonText = Drupal.t('Add file');
+          if (this.hasAttribute('multiple')) {
+            buttonText = Drupal.t('Add files');
+          }
+          const $button = $(
+            `<button class="button button--small js-dropzone-add-button">${buttonText}</button>`,
+          );
+          $button.insertAfter($wrapper.find('.form-managed-file'));
+
+          // Create dropzone HTML.
+          const $dropzone = $(`<div class="dropzone">
+            <div class="dropzone__trigger"></div>
+            <div class="dropzone__no-trigger">
+            </div>
+            </div>`);
+
+          // Insert dropzone before the wrapper and move the wrapper to the
+          // dropzone.
+          $dropzone
+            .insertBefore($wrapper)
+            .find('.dropzone__no-trigger')
+            .append($wrapper);
+
+          // If there is an image widget, move that before the file wrapper to
+          // allow easier theming.
+          $wrapper.find('.js-image-widget').insertAfter($wrapper);
+
+          // Attach listeners for drag/drop events.
+          $(this)
+            .on('dragover mouseenter', e => {
+              $(e.currentTarget)
+                .closest('.dropzone')
+                .find('.dropzone__trigger')
+                .addClass('is-hovering');
+            })
+            .on('dragleave mouseleave', e => {
+              $(e.currentTarget)
+                .closest('.dropzone')
+                .find('.dropzone__trigger')
+                .removeClass('is-hovering');
+            });
+        });
+
+      Drupal.ajax.instances
+        .filter(instance => {
+          if (instance && instance.element) {
+            const element = $(instance.element);
+            return (
+              element.length > 0 &&
+              element.hasClass('js-upload-button') &&
+              element.once('magic').length > 0
+            );
+          }
+          return null;
+        })
+        .forEach(instance => {
+          const $element = $(instance.element);
+
+          // Trigger the file field when the 'Add file' button is clicked to
+          // open the local file browser.
+          $element
+            .closest('.dropzone')
+            .find('.js-dropzone-add-button')
+            .on('click', e => {
+              e.preventDefault();
+              $(e.currentTarget)
+                .closest('.js-form-item')
+                .find('input[type="file"]')
+                .trigger('click');
+            });
+
+          // Hide the dropzone trigger when uploading files.
+          const beforeSend = instance.beforeSend;
+          $element.bind('beforeSend', beforeSend);
+          instance.beforeSend = function(xmlhttprequest, options) {
+            beforeSend.call(this, xmlhttprequest, options);
+            $element.trigger('beforeSend');
+            $element
+              .closest('.dropzone')
+              .find('.dropzone__trigger')
+              .addClass('is-hidden');
+          };
+        });
+
+      /**
+       * Fix the reflowing of the changed marker. Output after filesize span.
+       */
+      if (Drupal.tableDrag) {
+        Drupal.tableDrag.prototype.row.prototype.markChanged = function() {
+          const marker = Drupal.theme('tableDragChangedMarker');
+          const cell = $(this.element).find('td:first-of-type');
+          if (cell.find('abbr.tabledrag-changed').length === 0) {
+            // @todo Change the selector to '.js-file-size' when
+            //   https://www.drupal.org/project/drupal/issues/3033279 is fixed.
+            if (cell.find('.file-size').length) {
+              cell.find('.file-size').append(marker);
+            } else {
+              cell.append(marker);
+            }
+          }
+        };
+      }
+    },
+  };
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/themes/seven/js/dropzone.js b/core/themes/seven/js/dropzone.js
new file mode 100644
index 0000000000..77f50a7505
--- /dev/null
+++ b/core/themes/seven/js/dropzone.js
@@ -0,0 +1,86 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($, Drupal, drupalSettings) {
+  Drupal.behaviors.dropzone = {
+    attach: function attach(context) {
+      $(context).find('.js-form-managed-file-wrapper').addClass('form-managed-file-wrapper--dropzone').each(function () {
+        if ($(this).hasClass('has-file')) {
+          $(this).find('> .js-description').appendTo($('.js-image-widget-data', this));
+        }
+
+        var $files = $(this).find('.js-form-type-checkbox');
+        if ($files.length) {
+          var $filesWrapper = $('<div class="form-managed-file-items"></div>');
+          $files.appendTo($filesWrapper);
+          $filesWrapper.insertBefore($(this));
+          $(this).find('.js-remove-button-multiple').appendTo($filesWrapper);
+        }
+      });
+
+      $(context).find('.js-form-file').once('managed-file-dropzone').each(function () {
+        var $wrapper = $(this).parents('.js-form-managed-file-wrapper');
+
+        var buttonText = Drupal.t('Add file');
+        if (this.hasAttribute('multiple')) {
+          buttonText = Drupal.t('Add files');
+        }
+        var $button = $('<button class="button button--small js-dropzone-add-button">' + buttonText + '</button>');
+        $button.insertAfter($wrapper.find('.form-managed-file'));
+
+        var $dropzone = $('<div class="dropzone">\n            <div class="dropzone__trigger"></div>\n            <div class="dropzone__no-trigger">\n            </div>\n            </div>');
+
+        $dropzone.insertBefore($wrapper).find('.dropzone__no-trigger').append($wrapper);
+
+        $wrapper.find('.js-image-widget').insertAfter($wrapper);
+
+        $(this).on('dragover mouseenter', function (e) {
+          $(e.currentTarget).closest('.dropzone').find('.dropzone__trigger').addClass('is-hovering');
+        }).on('dragleave mouseleave', function (e) {
+          $(e.currentTarget).closest('.dropzone').find('.dropzone__trigger').removeClass('is-hovering');
+        });
+      });
+
+      Drupal.ajax.instances.filter(function (instance) {
+        if (instance && instance.element) {
+          var element = $(instance.element);
+          return element.length > 0 && element.hasClass('js-upload-button') && element.once('magic').length > 0;
+        }
+        return null;
+      }).forEach(function (instance) {
+        var $element = $(instance.element);
+
+        $element.closest('.dropzone').find('.js-dropzone-add-button').on('click', function (e) {
+          e.preventDefault();
+          $(e.currentTarget).closest('.js-form-item').find('input[type="file"]').trigger('click');
+        });
+
+        var beforeSend = instance.beforeSend;
+        $element.bind('beforeSend', beforeSend);
+        instance.beforeSend = function (xmlhttprequest, options) {
+          beforeSend.call(this, xmlhttprequest, options);
+          $element.trigger('beforeSend');
+          $element.closest('.dropzone').find('.dropzone__trigger').addClass('is-hidden');
+        };
+      });
+
+      if (Drupal.tableDrag) {
+        Drupal.tableDrag.prototype.row.prototype.markChanged = function () {
+          var marker = Drupal.theme('tableDragChangedMarker');
+          var cell = $(this.element).find('td:first-of-type');
+          if (cell.find('abbr.tabledrag-changed').length === 0) {
+            if (cell.find('.file-size').length) {
+              cell.find('.file-size').append(marker);
+            } else {
+              cell.append(marker);
+            }
+          }
+        };
+      }
+    }
+  };
+})(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml
index 6ae159416e..912d190ff8 100644
--- a/core/themes/seven/seven.libraries.yml
+++ b/core/themes/seven/seven.libraries.yml
@@ -15,10 +15,12 @@ global-styling:
       css/components/details.css: {}
       css/components/messages.css: {}
       css/components/dropbutton.component.css: {}
+      css/components/dropzone.css: {}
       css/components/entity-meta.css: {}
       css/components/field-ui.css: {}
       css/components/form.css: {}
       css/components/help.css: {}
+      css/components/managed-file.css: {}
       css/components/menus-and-lists.css: {}
       css/components/modules-page.css: {}
       css/components/node.css: {}
@@ -39,7 +41,11 @@ global-styling:
       css/theme/colors.css: {}
     layout:
       css/layout/layout.css: {}
+  js:
+    js/dropzone.js: {}
   dependencies:
+    - core/drupal.ajax
+    - core/drupal.progress
     - system/admin
 
 node-form:
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index 479a4e310a..ba0ed4dfdf 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
 use Drupal\media\MediaForm;
 
 /**
@@ -127,6 +128,35 @@ function seven_element_info_alter(&$type) {
   if (isset($type['button'])) {
     $type['button']['#attached']['library'][] = 'core/modernizr';
   }
+  if (isset($type['managed_file'])) {
+    $type['managed_file']['#process'][] = 'seven_process_managed_file';
+  }
+}
+
+/**
+ * Render API callback: Expands the managed_file element type.
+ *
+ * @return mixed
+ */
+function seven_process_managed_file($element) {
+  $element['upload']['#attributes']['class'][] = 'managed-file-upload';
+  $element['upload']['#weight'] = 1;
+  $element['upload_button']['#attributes']['class'][] = 'upload-button';
+  $element['upload_button']['#attributes']['class'][] = 'js-upload-button';
+  $element['upload_button']['#weight'] = 2;
+
+  // Add a different class to the remove button for multivalue fields since the
+  // remove button has different behavior for multivalue fields.
+  if ($element['#multiple']) {
+    $element['remove_button']['#attributes']['class'][] = 'button--small';
+    $element['remove_button']['#attributes']['class'][] = 'remove-button-multiple';
+    $element['remove_button']['#attributes']['class'][] = 'js-remove-button-multiple';
+  }
+  else {
+    $element['remove_button']['#attributes']['class'][] = 'remove-button';
+  }
+
+  return $element;
 }
 
 /**
@@ -145,6 +175,15 @@ function seven_preprocess_maintenance_page(&$variables) {
   $variables['#attached']['library'][] = 'seven/maintenance-page';
 }
 
+/**
+ * Implements hook_preprocess_file_widget_multiple().
+ */
+function seven_preprocess_file_widget_multiple(&$variables) {
+  foreach (Element::children($variables['element']) as $key) {
+    $variables['element'][$key]['#title_display'] = 'invisible';
+  }
+}
+
 /**
  * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
  *
@@ -164,6 +203,13 @@ function seven_form_node_form_alter(&$form, FormStateInterface $form_state) {
   $form['revision_information']['#group'] = 'meta';
 }
 
+/**
+ * Implements hook_theme_suggestions_form_element_alter().
+ */
+function seven_theme_suggestions_form_element_alter(&$suggestions, $variables) {
+  $suggestions[] = 'form_element__' . $variables['element']['#type'];
+}
+
 /**
  * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\media\MediaForm.
  */
diff --git a/core/themes/seven/templates/file-upload-help.html.twig b/core/themes/seven/templates/file-upload-help.html.twig
new file mode 100644
index 0000000000..5a19d30555
--- /dev/null
+++ b/core/themes/seven/templates/file-upload-help.html.twig
@@ -0,0 +1,16 @@
+{#
+/**
+ * @file
+ * Theme override to display help text for file fields.
+ *
+ * Available variables:
+ * - descriptions: Lines of help text for uploading a file.
+ *
+ * @see template_preprocess_file_upload_help()
+ */
+#}
+<ul class="file-upload-help">
+  {% for description in descriptions %}
+    <li>{{ description|trim('.') }}</li>
+  {% endfor %}
+</ul>
diff --git a/core/themes/seven/templates/file-widget-multiple.html.twig b/core/themes/seven/templates/file-widget-multiple.html.twig
new file mode 100644
index 0000000000..a0212a2d3c
--- /dev/null
+++ b/core/themes/seven/templates/file-widget-multiple.html.twig
@@ -0,0 +1,16 @@
+{#
+/**
+ * @file
+ * Theme override to display a multi file form widget.
+ *
+ * Available variables:
+ * - table: Table of previously uploaded files.
+ * - element: The form element for uploading another file.
+ *
+ * @see template_preprocess_file_widget_multiple()
+ */
+#}
+<div class="form-type-managed-file">
+  {{ table }}
+  {{ element }}
+</div>
diff --git a/core/themes/seven/templates/form-element--managed-file.html.twig b/core/themes/seven/templates/form-element--managed-file.html.twig
new file mode 100644
index 0000000000..3458fe091b
--- /dev/null
+++ b/core/themes/seven/templates/form-element--managed-file.html.twig
@@ -0,0 +1,98 @@
+{#
+/**
+ * @file
+ * Theme override for a form element.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - errors: (optional) Any errors for this form element, may not be set.
+ * - prefix: (optional) The form element prefix, may not be set.
+ * - suffix: (optional) The form element suffix, may not be set.
+ * - required: The required marker, or empty if the associated form element is
+ *   not required.
+ * - type: The type of the element.
+ * - name: The name of the element.
+ * - label: A rendered label element.
+ * - label_display: Label display setting. It can have these values:
+ *   - before: The label is output before the element. This is the default.
+ *     The label includes the #title and the required marker, if #required.
+ *   - after: The label is output after the element. For example, this is used
+ *     for radio and checkbox #type elements. If the #title is empty but the
+ *     field is #required, the label will contain only the required marker.
+ *   - invisible: Labels are critical for screen readers to enable them to
+ *     properly navigate through forms but can be visually distracting. This
+ *     property hides the label for everyone except screen readers.
+ *   - attribute: Set the title attribute on the element to create a tooltip but
+ *     output no label element. This is supported only for checkboxes and radios
+ *     in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
+ *     It is used where a visual label is not needed, such as a table of
+ *     checkboxes where the row and column provide the context. The tooltip will
+ *     include the title and required marker.
+ * - description: (optional) A list of description properties containing:
+ *    - content: A description of the form element, may not be set.
+ *    - attributes: (optional) A list of HTML attributes to apply to the
+ *      description content wrapper. Will only be set when description is set.
+ * - description_display: Description display setting. It can have these values:
+ *   - before: The description is output before the element.
+ *   - after: The description is output after the element. This is the default
+ *     value.
+ *   - invisible: The description is output after the element, hidden visually
+ *     but available to screen readers.
+ * - disabled: True if the element is disabled.
+ * - title_display: Title display setting.
+ *
+ * @see template_preprocess_form_element()
+ */
+#}
+{%
+  set classes = [
+    'js-form-item',
+    'form-item',
+    'js-form-type-' ~ type|clean_class,
+    'form-type-' ~ type|clean_class,
+    'js-form-item-' ~ name|clean_class,
+    'form-item-' ~ name|clean_class,
+    title_display not in ['after', 'before'] ? 'form-no-label',
+    disabled == 'disabled' ? 'form-disabled',
+    errors ? 'form-item--error',
+  ]
+%}
+{%
+  set description_classes = [
+    'description',
+    'js-description',
+    description_display == 'invisible' ? 'visually-hidden',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
+  {% if label_display in ['before', 'invisible'] %}
+    {{ label }}
+  {% endif %}
+  {% if prefix is not empty %}
+    <span class="field-prefix">{{ prefix }}</span>
+  {% endif %}
+  {% if description_display == 'before' and description.content %}
+    <div{{ description.attributes }}>
+      {{ description.content }}
+    </div>
+  {% endif %}
+  {% if errors %}
+    <div class="form-item--error-message">
+      <strong>{{ errors }}</strong>
+    </div>
+  {% endif %}
+  <div class="form-managed-file-wrapper js-form-managed-file-wrapper {% if not element['#multiple'] and element['#value']['fids'] is not empty %} has-file{% endif %}">
+    {{ children }}
+    {% if description_display in ['after', 'invisible'] and description.content %}
+      <div{{ description.attributes.addClass(description_classes) }}>
+        {{ description.content }}
+      </div>
+    {% endif %}
+  </div>
+  {% if suffix is not empty %}
+    <span class="field-suffix">{{ suffix }}</span>
+  {% endif %}
+  {% if label_display == 'after' %}
+    {{ label }}
+  {% endif %}
+</div>
diff --git a/core/themes/seven/templates/image-widget.html.twig b/core/themes/seven/templates/image-widget.html.twig
index a7cf3ad48a..a2c7509fd8 100644
--- a/core/themes/seven/templates/image-widget.html.twig
+++ b/core/themes/seven/templates/image-widget.html.twig
@@ -8,5 +8,14 @@
  * @see template_preprocess_image_widget()
  */
 #}
-{% include '@classy/content-edit/image-widget.html.twig' %}
-{{ attach_library('classy/image-widget') }}
+<div{{ attributes.addClass('js-image-widget') }}>
+  {% if data.preview %}
+    <div class="image-preview">
+      {{ data.preview }}
+    </div>
+  {% endif %}
+  <div class="image-widget-data js-image-widget-data">
+    {# Render widget data without the image preview that was output already. #}
+    {{ data|without('preview') }}
+  </div>
+</div>
