diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index b05de53a0fe1290ecafc570bd6d8ac18d74306ea..e93c71b10e8593eb0d251906659f66f1cfedf1b5 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -283,6 +283,10 @@ Media
 - Christian Fritsch 'chr.fritsch' https://www.drupal.org/u/chr.fritsch
 - Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima
 
+  Media Library
+  - Sean Blommaert 'seanB' https://www.drupal.org/u/seanb
+  - Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima
+
 Menu
 - Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner
 - Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
diff --git a/core/modules/media_library/config/install/views.view.media_library.yml b/core/modules/media_library/config/install/views.view.media_library.yml
index d2799f92968b163aaf990ebf596d7c0ef3c920e9..32fdbe4a3d0347b8ccb0429d7493efdc9de55505 100644
--- a/core/modules/media_library/config/install/views.view.media_library.yml
+++ b/core/modules/media_library/config/install/views.view.media_library.yml
@@ -73,7 +73,7 @@ display:
         type: default
         options:
           grouping: {  }
-          row_class: 'media-library-item media-library-item--grid js-media-library-item js-click-to-select'
+          row_class: ''
           default_row_class: true
       row:
         type: fields
@@ -120,7 +120,7 @@ display:
             preserve_tags: ''
             html: false
           element_type: ''
-          element_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
+          element_class: ''
           element_label_type: ''
           element_label_class: ''
           element_label_colon: false
@@ -173,7 +173,7 @@ display:
             preserve_tags: ''
             html: false
           element_type: ''
-          element_class: media-library-item__content
+          element_class: ''
           element_label_type: ''
           element_label_class: ''
           element_label_colon: false
@@ -468,7 +468,7 @@ display:
       relationships: {  }
       display_extenders: {  }
       use_ajax: true
-      css_class: 'media-library-view js-media-library-view'
+      css_class: ''
     cache_metadata:
       max-age: 0
       contexts:
@@ -525,7 +525,7 @@ display:
             preserve_tags: ''
             html: false
           element_type: ''
-          element_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
+          element_class: ''
           element_label_type: ''
           element_label_class: ''
           element_label_colon: false
@@ -627,7 +627,7 @@ display:
             trim_whitespace: false
             alt: 'Edit {{ name }}'
             rel: ''
-            link_class: media-library-item__edit
+            link_class: ''
             prefix: ''
             suffix: ''
             target: ''
@@ -680,7 +680,7 @@ display:
             trim_whitespace: false
             alt: 'Delete {{ name }}'
             rel: ''
-            link_class: media-library-item__remove
+            link_class: ''
             prefix: ''
             suffix: ''
             target: ''
@@ -749,7 +749,7 @@ display:
             preserve_tags: ''
             html: false
           element_type: ''
-          element_class: media-library-item__content
+          element_class: ''
           element_label_type: ''
           element_label_class: ''
           element_label_colon: false
@@ -786,10 +786,10 @@ display:
       display_extenders: {  }
       path: admin/content/media-widget
       fields:
-        rendered_entity:
-          id: rendered_entity
+        media_library_select_form:
+          id: media_library_select_form
           table: media
-          field: rendered_entity
+          field: media_library_select_form
           relationship: none
           group_type: group
           admin_label: ''
@@ -823,7 +823,7 @@ display:
             preserve_tags: ''
             html: false
           element_type: ''
-          element_class: media-library-item__content
+          element_class: ''
           element_label_type: ''
           element_label_class: ''
           element_label_colon: false
@@ -834,13 +834,12 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          view_mode: media_library
           entity_type: media
-          plugin_id: rendered_entity
-        media_library_select_form:
-          id: media_library_select_form
+          plugin_id: media_library_select_form
+        rendered_entity:
+          id: rendered_entity
           table: media
-          field: media_library_select_form
+          field: rendered_entity
           relationship: none
           group_type: group
           admin_label: ''
@@ -879,14 +878,15 @@ display:
           element_label_class: ''
           element_label_colon: false
           element_wrapper_type: ''
-          element_wrapper_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
+          element_wrapper_class: ''
           element_default_classes: true
           empty: ''
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          view_mode: media_library
           entity_type: media
-          plugin_id: media_library_select_form
+          plugin_id: rendered_entity
       defaults:
         fields: false
         access: false
@@ -1086,7 +1086,7 @@ display:
           label: 'Table'
           plugin_id: display_link
           empty: true
-      css_class: 'media-library-view js-media-library-view media-library-view--widget'
+      css_class: ''
       rendering_language: '***LANGUAGE_language_interface***'
     cache_metadata:
       max-age: -1
@@ -1131,7 +1131,7 @@ display:
           relationship: none
           entity_type: media
           plugin_id: media_library_select_form
-          element_wrapper_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
+          element_wrapper_class: ''
           element_class: ''
         thumbnail__target_id:
           id: thumbnail__target_id
@@ -1374,7 +1374,7 @@ display:
           label: 'Table'
           plugin_id: display_link
           empty: true
-      css_class: 'media-library-view js-media-library-view media-library-view--widget'
+      css_class: ''
       rendering_language: '***LANGUAGE_language_interface***'
     cache_metadata:
       max-age: -1
diff --git a/core/modules/media_library/css/media_library.module.css b/core/modules/media_library/css/media_library.module.css
deleted file mode 100644
index 968cba67f52e34883a283e21e63d0c758504aa5a..0000000000000000000000000000000000000000
--- a/core/modules/media_library/css/media_library.module.css
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
-* @file media_library.module.css
-*/
-
-/**
- * By default, the dialog is too narrow to be usable.
- * @see Drupal.ckeditor.openDialog()
- */
-.ui-dialog--narrow.media-library-widget-modal {
-  max-width: 75%;
-}
-
-.media-library-wrapper {
-  display: flex;
-}
-
-.media-library-menu {
-  margin: 0;
-  padding: 0;
-}
-
-/* @todo Use a class instead of the li element.
-     https://www.drupal.org/project/drupal/issues/3029227 */
-.media-library-menu li {
-  padding: 0;
-  list-style: none;
-}
-
-.media-library-views-form > .form-actions {
-  flex-basis: 100%;
-}
-
-.media-library-views-form,
-.media-library-selection,
-.media-library-add-form__selected-media .details-wrapper,
-.media-library-views-form__bulk_form,
-.media-library-view .form--inline {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.media-library-views-form__header {
-  flex-basis: 100%;
-}
-
-.media-library-item {
-  position: relative;
-}
-
-.media-library-item__click-to-select-trigger {
-  overflow: hidden;
-  height: 100%;
-  cursor: pointer;
-}
-
-.media-library-view .form-actions {
-  align-self: flex-end;
-}
-
-.media-library-item__click-to-select-checkbox {
-  position: absolute;
-  z-index: 1;
-  top: 16px;
-  left: 16px; /* LTR */
-  display: block;
-}
-[dir="rtl"] .media-library-item__click-to-select-checkbox {
-  right: 16px;
-  left: auto;
-}
-
-.media-library-item__status {
-  position: absolute;
-  top: 40px;
-  left: 5px; /* LTR */
-  pointer-events: none;
-}
-[dir="rtl"] .media-library-item__status {
-  right: 5px;
-  left: auto;
-}
-
-.media-library-select-all {
-  flex-basis: 100%;
-  width: 100%;
-}
-
-.media-library-view--widget .media-library-select-all {
-  display: none;
-}
-
-.media-library-item--disabled {
-  pointer-events: none;
-}
-
-.media-library-selection .media-library-item__preview {
-  cursor: move;
-}
-
-.media-library-widget-modal .ui-dialog-buttonpane {
-  display: flex;
-  align-items: center;
-}
-
-.media-library-widget-modal .ui-dialog-buttonpane .form-actions {
-  flex: 1;
-}
-
-@media screen and (max-width: 600px) {
-  .media-library-view .form-actions {
-    flex-basis: 100%;
-  }
-}
-
-/* @todo Remove in https://www.drupal.org/project/drupal/issues/3064914 */
-.views-live-preview .media-library-view div.views-row + div.views-row {
-  margin-top: 0;
-}
diff --git a/core/modules/media_library/js/media_library.view.es6.js b/core/modules/media_library/js/media_library.view.es6.js
index 9b28c72f482ae271436a829984da0d3e5aaec227..311968ccd12f3f72b99d1d35c28e8fbfd4be89b6 100644
--- a/core/modules/media_library/js/media_library.view.es6.js
+++ b/core/modules/media_library/js/media_library.view.es6.js
@@ -12,9 +12,10 @@
    */
   Drupal.behaviors.MediaLibrarySelectAll = {
     attach(context) {
-      const $view = $('.js-media-library-view', context).once(
-        'media-library-select-all',
-      );
+      const $view = $(
+        '.js-media-library-view[data-view-display-id="page"]',
+        context,
+      ).once('media-library-select-all');
       if ($view.length && $view.find('.js-media-library-item').length) {
         const $checkbox = $(Drupal.theme('checkbox')).on(
           'click',
diff --git a/core/modules/media_library/js/media_library.view.js b/core/modules/media_library/js/media_library.view.js
index b3c21e9eb8cc9054ed2df0493c907078e22ae790..099991a32a716de38d5d8d8ff83a7373c11a8eae 100644
--- a/core/modules/media_library/js/media_library.view.js
+++ b/core/modules/media_library/js/media_library.view.js
@@ -8,7 +8,7 @@
 (function ($, Drupal) {
   Drupal.behaviors.MediaLibrarySelectAll = {
     attach: function attach(context) {
-      var $view = $('.js-media-library-view', context).once('media-library-select-all');
+      var $view = $('.js-media-library-view[data-view-display-id="page"]', context).once('media-library-select-all');
       if ($view.length && $view.find('.js-media-library-item').length) {
         var $checkbox = $(Drupal.theme('checkbox')).on('click', function (_ref) {
           var currentTarget = _ref.currentTarget;
diff --git a/core/modules/media_library/media_library.info.yml b/core/modules/media_library/media_library.info.yml
index fbda817c5bfe154d05e0c41d4d122a5f745de766..7985e3d26ad76e86890239de9aa589a5603cb3f3 100644
--- a/core/modules/media_library/media_library.info.yml
+++ b/core/modules/media_library/media_library.info.yml
@@ -1,7 +1,7 @@
 name: 'Media Library'
 type: module
 description: 'Enhances the media list with additional features to more easily find and use existing media items.'
-package: Core (Experimental)
+package: Core
 version: VERSION
 dependencies:
   - drupal:media
diff --git a/core/modules/media_library/media_library.libraries.yml b/core/modules/media_library/media_library.libraries.yml
index 02ecd2a6e18143cd2bb8df5b0f33616ec4c0f3bf..b548e42549846db7596e5bc899f32b56f320799f 100644
--- a/core/modules/media_library/media_library.libraries.yml
+++ b/core/modules/media_library/media_library.libraries.yml
@@ -1,11 +1,3 @@
-style:
-  version: VERSION
-  css:
-    component:
-      css/media_library.module.css: {}
-    theme:
-      css/media_library.theme.css: {}
-
 click_to_select:
   version: VERSION
   js:
@@ -21,7 +13,6 @@ view:
   dependencies:
     - core/drupal.announce
     - core/drupal.checkbox
-    - media_library/style
     - media_library/click_to_select
 
 widget:
@@ -31,7 +22,6 @@ widget:
   dependencies:
     - core/drupal.dialog.ajax
     - core/jquery.once
-    - media_library/style
     - core/sortable
 
 ui:
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index 6822601f9877e16b0df049edaca148290c76313a..84c0f67732fc1bc5311e476179f5e57d2ff4268d 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -24,7 +24,6 @@
 use Drupal\media_library\Form\FileUploadForm;
 use Drupal\media_library\Form\OEmbedForm;
 use Drupal\media_library\MediaLibraryState;
-use Drupal\views\Form\ViewsForm;
 use Drupal\views\Plugin\views\cache\CachePluginBase;
 use Drupal\views\ViewExecutable;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -60,11 +59,21 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
       $output .= '</dl>';
       $output .= '<h3>' . t('Customize') . '</h3>';
       $output .= '<ul>';
+      $output .= '<li>';
+      if (\Drupal::moduleHandler()->moduleExists('views_ui') && \Drupal::currentUser()->hasPermission('administer views')) {
+        $output .= t('Both the table-style and grid-style interfaces are regular views and can be customized via the <a href=":views-ui">Views UI</a>, including sorting and filtering. This is the case for both the administration page and the modal dialog.', [
+          ':views_ui' => Url::fromRoute('entity.view.collection')->toString(),
+        ]);
+      }
+      else {
+        $output .= t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.');
+      }
+      $output .= '</li>';
       $output .= '<li>' . t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.') . '</li>';
-      $output .= '<li>' . t('In the grid-style interface, which fields are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
+      $output .= '<li>' . t('In the grid-style interface, the fields that are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
         ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
       ]) . '</li>';
-      $output .= '<li>' . t('When adding new media items within the modal dialog, which fields are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
+      $output .= '<li>' . t('When adding new media items within the modal dialog, the fields that are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
         ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
       ]) . '</li>';
       $output .= '</ul>';
@@ -91,9 +100,84 @@ function media_library_theme() {
     'media__media_library' => [
       'base hook' => 'media',
     ],
+    'media_library_wrapper' => [
+      'render element' => 'element',
+    ],
+    'media_library_item' => [
+      'render element' => 'element',
+    ],
   ];
 }
 
+/**
+ * Prepares variables for the media library modal dialog.
+ *
+ * Default template: media-library-wrapper.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #menu, #content.
+ */
+function template_preprocess_media_library_wrapper(array &$variables) {
+  $variables['menu'] = &$variables['element']['menu'];
+  $variables['content'] = &$variables['element']['content'];
+}
+
+/**
+ * Prepares variables for a selected media item.
+ *
+ * Default template: media-library-item.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of
+ *     the element.
+ */
+function template_preprocess_media_library_item(array &$variables) {
+  $element = &$variables['element'];
+  foreach (Element::children($element) as $key) {
+    $variables['content'][$key] = $element[$key];
+  }
+}
+
+/**
+ * Implements hook_views_pre_render().
+ */
+function media_library_views_pre_render(ViewExecutable $view) {
+  $add_classes = function (&$option, array $classes_to_add) {
+    $classes = $option ? preg_split('/\s+/', trim($option)) : [];
+    $classes = array_filter($classes);
+    $classes = array_merge($classes, $classes_to_add);
+    $option = implode(' ', array_unique($classes));
+  };
+
+  if ($view->id() === 'media_library') {
+    if ($view->current_display === 'page') {
+      $add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
+
+      if (array_key_exists('media_bulk_form', $view->field)) {
+        $add_classes($view->field['media_bulk_form']->options['element_class'], ['js-click-to-select-checkbox']);
+      }
+    }
+    elseif (strpos($view->current_display, 'widget') === 0) {
+      if (array_key_exists('media_library_select_form', $view->field)) {
+        $add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['js-click-to-select-checkbox']);
+      }
+      $add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
+    }
+
+    $add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
+
+    if ($view->display_handler->options['defaults']['css_class']) {
+      $add_classes($view->displayHandlers->get('default')->options['css_class'], ['js-media-library-view']);
+    }
+    else {
+      $add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
+    }
+  }
+}
+
 /**
  * Implements hook_views_post_render().
  */
@@ -143,23 +227,30 @@ function media_library_preprocess_media(&$variables) {
     $variables['url'] = $media->toUrl($rel, [
       'language' => $media->language(),
     ]);
-    $variables['preview_attributes'] = new Attribute();
-    $variables['preview_attributes']->addClass('media-library-item__preview', 'js-media-library-item-preview');
-    $variables['metadata_attributes'] = new Attribute();
-    $variables['metadata_attributes']->addClass('media-library-item__attributes');
+    $variables += [
+      'preview_attributes' => new Attribute(),
+      'metadata_attributes' => new Attribute(),
+    ];
     $variables['status'] = $media->isPublished();
   }
 }
 
+/**
+ * Implements hook_preprocess_views_view() for the 'media_library' view.
+ */
+function media_library_preprocess_views_view__media_library(array &$variables) {
+  $variables['attributes']['data-view-display-id'] = $variables['view']->current_display;
+}
+
 /**
  * Implements hook_preprocess_views_view_fields().
  */
 function media_library_preprocess_views_view_fields(&$variables) {
   // Add classes to media rendered entity field so it can be targeted for
-  // styling and JavaScript mouseover and click events.
+  // JavaScript mouseover and click events.
   if ($variables['view']->id() === 'media_library' && isset($variables['fields']['rendered_entity'])) {
     if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
-      $variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger media-library-item__click-to-select-trigger');
+      $variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger');
     }
   }
 }
@@ -193,41 +284,32 @@ function media_library_form_views_form_media_library_page_alter(array &$form, Fo
  * Implements hook_form_alter().
  */
 function media_library_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
-  $form_object = $form_state->getFormObject();
-  if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
-    $form['#attributes']['class'][] = 'media-library-views-form';
-    if (isset($form['header'])) {
-      $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
-      $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
-    }
-  }
-
-  // Add after build to fix media library views exposed filter's submit button.
+  // Add a process callback to ensure that the media library view's exposed
+  // filters submit button is not moved to the modal dialog's button area.
   if ($form_id === 'views_exposed_form' && strpos($form['#id'], 'views-exposed-form-media-library-widget') === 0) {
     $form['#after_build'][] = '_media_library_views_form_media_library_after_build';
   }
 
   // Configures media_library displays when a type is submitted.
-  if ($form_object instanceof MediaTypeForm) {
+  if ($form_state->getFormObject() instanceof MediaTypeForm) {
     $form['actions']['submit']['#submit'][] = '_media_library_media_type_form_submit';
   }
 }
 
 /**
- * After build callback for views form media library.
+ * Form #after_build callback for media_library view's exposed filters form.
  */
 function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
-  // Remove .form-actions from media library views exposed filter actions
-  // and replace with .media-library-view--form-actions.
-  //
-  // This prevents the views exposed filter's 'Apply filter' submit button from
-  // being moved into the dialog's buttons.
+  // Remove .form-actions from the view's exposed filter actions. This prevents
+  // the "Apply filters" submit button from being moved into the dialog's
+  // button area.
   // @see \Drupal\Core\Render\Element\Actions::processActions
   // @see Drupal.behaviors.dialog.prepareDialogButtons
+  // @todo Remove this after
+  //   https://www.drupal.org/project/drupal/issues/3089751 is fixed.
   if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
     unset($form['actions']['#attributes']['class'][$key]);
   }
-  $form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
   return $form;
 }
 
diff --git a/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php b/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php
index c9f329ce6bccbb9d0178599c7fcb149bc7e93ce0..1f6e7e047b46cd975936eb493c536de25a4b0bb7 100644
--- a/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php
+++ b/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php
@@ -18,9 +18,8 @@
  * @ingroup ajax
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   This is an internal part of Media Library and may be subject to change in
+ *   minor releases. External code should not instantiate or extend this class.
  */
 class UpdateSelectionCommand implements CommandInterface {
 
diff --git a/core/modules/media_library/src/Form/AddFormBase.php b/core/modules/media_library/src/Form/AddFormBase.php
index 331fec0b84e0a2d65b8af45c8d3712db90b2af90..497d83346bb53d704c0cb0ffb5b51ef3c20885be 100644
--- a/core/modules/media_library/src/Form/AddFormBase.php
+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -9,8 +9,11 @@
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\BaseFormIdInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Url;
 use Drupal\media\MediaInterface;
 use Drupal\media\MediaTypeInterface;
@@ -21,13 +24,8 @@
 
 /**
  * Provides a base class for creating media items from within the media library.
- *
- * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
  */
-abstract class AddFormBase extends FormBase {
+abstract class AddFormBase extends FormBase implements BaseFormIdInterface, TrustedCallbackInterface {
 
   /**
    * The entity type manager.
@@ -99,7 +97,7 @@ public static function create(ContainerInterface $container) {
   /**
    * {@inheritdoc}
    */
-  public function getFormId() {
+  public function getBaseFormId() {
     return 'media_library_add_form';
   }
 
@@ -137,9 +135,8 @@ protected function getMediaType(FormStateInterface $form_state) {
   public function buildForm(array $form, FormStateInterface $form_state) {
     // @todo Remove the ID when we can use selectors to replace content via
     //   AJAX in https://www.drupal.org/project/drupal/issues/2821793.
-    $form['#prefix'] = '<div id="media-library-add-form-wrapper" class="media-library-add-form-wrapper">';
+    $form['#prefix'] = '<div id="media-library-add-form-wrapper">';
     $form['#suffix'] = '</div>';
-    $form['#attached']['library'][] = 'media_library/style';
 
     // The media library is loaded via AJAX, which means that the form action
     // URL defaults to the current URL. However, to add media, we always need to
@@ -157,27 +154,37 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     ];
 
     $form['#attributes']['class'] = [
-      'media-library-add-form',
       'js-media-library-add-form',
     ];
 
     $added_media = $this->getAddedMediaItems($form_state);
     if (empty($added_media)) {
-      $form['#attributes']['class'][] = 'media-library-add-form--without-input';
       $form = $this->buildInputElement($form, $form_state);
     }
     else {
-      $form['#attributes']['class'][] = 'media-library-add-form--with-input';
+      $form['#attributes']['data-input'] = 'true';
+
+      // This deserves to be themeable, but it doesn't need to be its own "real"
+      // template.
+      $form['description'] = [
+        '#type' => 'inline_template',
+        '#template' => '<p>{{ text }}</p>',
+        '#context' => [
+          'text' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
+        ],
+      ];
 
       $form['media'] = [
-        '#type' => 'container',
+        '#pre_render' => [
+          [$this, 'preRenderAddedMedia'],
+        ],
         '#attributes' => [
           'class' => [
+            // This needs to be focus-able by an AJAX response.
+            // @see ::updateFormCallback()
             'js-media-library-add-form-added-media',
-            'media-library-add-form__added-media',
           ],
           'aria-label' => $this->t('Added media items'),
-          'role' => 'list',
           // Add the tabindex '-1' to allow the focus to be shifted to the added
           // media wrapper when items are added. We set focus to the container
           // because a media item does not necessarily have required fields and
@@ -186,18 +193,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
           'tabindex' => '-1',
         ],
       ];
-
-      $form['media']['description'] = [
-        '#type' => 'html_tag',
-        '#tag' => 'p',
-        '#value' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
-        '#attributes' => [
-          'class' => [
-            'media-library-add-form__description',
-          ],
-        ],
-      ];
-
       foreach ($added_media as $delta => $media) {
         $form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
       }
@@ -267,13 +262,8 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
     $id_suffix = $parents ? '-' . implode('-', $parents) : '';
 
     $element = [
-      '#type' => 'container',
-      '#attributes' => [
-        'class' => [
-          'media-library-add-form__media',
-        ],
+      '#wrapper_attributes' => [
         'aria-label' => $media->getName(),
-        'role' => 'listitem',
         // Add the tabindex '-1' to allow the focus to be shifted to the next
         // media item when an item is removed. We set focus to the container
         // because a media item does not necessarily have required fields and we
@@ -288,20 +278,10 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
       'preview' => [
         '#type' => 'container',
         '#weight' => 10,
-        '#attributes' => [
-          'class' => [
-            'media-library-add-form__preview',
-          ],
-        ],
       ],
       'fields' => [
         '#type' => 'container',
         '#weight' => 20,
-        '#attributes' => [
-          'class' => [
-            'media-library-add-form__fields',
-          ],
-        ],
         // The '#parents' are set here because the entity form display needs it
         // to build the entity form fields.
         '#parents' => ['media', $delta, 'fields'],
@@ -312,7 +292,6 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
         '#name' => 'media-' . $delta . '-remove-button' . $id_suffix,
         '#weight' => 30,
         '#attributes' => [
-          'class' => ['media-library-add-form__remove-button'],
           'aria-label' => $this->t('Remove @label', ['@label' => $media->getName()]),
         ],
         '#ajax' => [
@@ -349,13 +328,13 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
     }
     $form_display->buildForm($media, $element['fields'], $form_state);
 
-    // We hide the preview of the uploaded file in the image widget with CSS.
+    // We hide the preview of the uploaded file in the image widget with CSS, so
+    // set a property so themes and form_alter hooks can easily identify the
+    // source field.
     // @todo Improve hiding file widget elements in
     //   https://www.drupal.org/project/drupal/issues/2987921
-    $source_field_name = $this->getSourceFieldName($media->bundle->entity);
-    if (isset($element['fields'][$source_field_name])) {
-      $element['fields'][$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
-    }
+    $element['fields']['#source_field_name'] = $this->getSourceFieldName($media->bundle->entity);
+
     // The revision log field is currently not configurable from the form
     // display, so hide it by changing the access.
     // @todo Make the revision_log_message field configurable in
@@ -366,6 +345,34 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
     return $element;
   }
 
+  /**
+   * {@inheritdodc}
+   */
+  public static function trustedCallbacks() {
+    return ['preRenderAddedMedia'];
+  }
+
+  /**
+   * Converts the set of newly added media into an item list for rendering.
+   *
+   * @param array $element
+   *   The render element to transform.
+   *
+   * @return array
+   *   The transformed render element.
+   */
+  public function preRenderAddedMedia(array $element) {
+    // Transform the element into an item list for rendering.
+    $element['#theme'] = 'item_list__media_library_add_form_media_list';
+    $element['#list_type'] = 'ul';
+
+    foreach (Element::children($element) as $delta) {
+      $element['#items'][$delta] = $element[$delta];
+      unset($element[$delta]);
+    }
+    return $element;
+  }
+
   /**
    * Returns a render array containing the current selection.
    *
@@ -386,13 +393,11 @@ protected function buildCurrentSelectionArea(array $form, FormStateInterface $fo
 
     $selection = [
       '#type' => 'details',
+      '#theme_wrappers' => [
+        'details__media_library_add_form_selected_media',
+      ],
       '#open' => FALSE,
       '#title' => $this->t('Additional selected media'),
-      '#attributes' => [
-        'class' => [
-          'media-library-add-form__selected-media',
-        ],
-      ],
     ];
     foreach ($pre_selected_items as $media_id => $media) {
       $selection[$media_id] = $this->buildSelectedItemElement($media, $form, $form_state);
@@ -416,12 +421,9 @@ protected function buildCurrentSelectionArea(array $form, FormStateInterface $fo
    */
   protected function buildSelectedItemElement(MediaInterface $media, array $form, FormStateInterface $form_state) {
     return [
-      '#type' => 'container',
+      '#theme' => 'media_library_item__small',
       '#attributes' => [
         'class' => [
-          'media-library-item',
-          'media-library-item--grid',
-          'media-library-item--small',
           'js-media-library-item',
           'js-click-to-select',
         ],
@@ -430,7 +432,7 @@ protected function buildSelectedItemElement(MediaInterface $media, array $form,
         '#type' => 'container',
         '#attributes' => [
           'class' => [
-            'js-click-to-select-checkbox media-library-item__click-to-select-checkbox',
+            'js-click-to-select-checkbox',
           ],
         ],
         'select_checkbox' => [
diff --git a/core/modules/media_library/src/Form/FileUploadForm.php b/core/modules/media_library/src/Form/FileUploadForm.php
index 81367821c25fca550eeb5afe4102ee75213e7729..d9b377d4a1b8fd8b2eb9f5d6fa807f9a39d4e07b 100644
--- a/core/modules/media_library/src/Form/FileUploadForm.php
+++ b/core/modules/media_library/src/Form/FileUploadForm.php
@@ -27,9 +27,7 @@
  * Creates a form to create media entities from uploaded files.
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   Form classes are internal.
  */
 class FileUploadForm extends AddFormBase {
 
@@ -106,6 +104,13 @@ public static function create(ContainerInterface $container) {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return $this->getBaseFormId() . '_upload';
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -128,8 +133,6 @@ protected function getMediaType(FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   protected function buildInputElement(array $form, FormStateInterface $form_state) {
-    $form['#attributes']['class'][] = 'media-library-add-form--upload';
-
     // Create a file item to get the upload validators.
     $media_type = $this->getMediaType($form_state);
     $item = $this->createFileItem($media_type);
@@ -145,9 +148,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
     // Add a container to group the input elements for styling purposes.
     $form['container'] = [
       '#type' => 'container',
-      '#attributes' => [
-        'class' => ['media-library-add-form__input-wrapper'],
-      ],
     ];
 
     $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
diff --git a/core/modules/media_library/src/Form/OEmbedForm.php b/core/modules/media_library/src/Form/OEmbedForm.php
index c31bc8e0dd3fd65a39980331b395afe65b37adb4..8b70c896918ee27fa078cd8202b704955408c07e 100644
--- a/core/modules/media_library/src/Form/OEmbedForm.php
+++ b/core/modules/media_library/src/Form/OEmbedForm.php
@@ -18,9 +18,7 @@
  * Creates a form to create media entities from oEmbed URLs.
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   Form classes are internal.
  */
 class OEmbedForm extends AddFormBase {
 
@@ -71,6 +69,13 @@ public static function create(ContainerInterface $container) {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return $this->getBaseFormId() . '_oembed';
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -90,17 +95,12 @@ protected function getMediaType(FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   protected function buildInputElement(array $form, FormStateInterface $form_state) {
-    $form['#attributes']['class'][] = 'media-library-add-form--oembed';
-
     $media_type = $this->getMediaType($form_state);
     $providers = $media_type->getSource()->getProviders();
 
     // Add a container to group the input elements for styling purposes.
     $form['container'] = [
       '#type' => 'container',
-      '#attributes' => [
-        'class' => ['media-library-add-form__input-wrapper'],
-      ],
     ];
 
     $form['container']['url'] = [
@@ -114,7 +114,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
       '#required' => TRUE,
       '#attributes' => [
         'placeholder' => 'https://',
-        'class' => ['media-library-add-form-oembed-url'],
       ],
     ];
 
@@ -139,9 +138,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
           ],
         ],
       ],
-      '#attributes' => [
-        'class' => ['media-library-add-form-oembed-submit'],
-      ],
     ];
     return $form;
   }
diff --git a/core/modules/media_library/src/Form/SettingsForm.php b/core/modules/media_library/src/Form/SettingsForm.php
index c171ece8088125c53ef95f9bbad719f648dfd105..68193583f585cf5ba5f04f3b56ed8f37ef697b0d 100644
--- a/core/modules/media_library/src/Form/SettingsForm.php
+++ b/core/modules/media_library/src/Form/SettingsForm.php
@@ -7,6 +7,9 @@
 
 /**
  * Defines a form for configuring the Media Library module.
+ *
+ * @internal
+ *   Form classes are internal.
  */
 class SettingsForm extends ConfigFormBase {
 
diff --git a/core/modules/media_library/src/MediaLibraryEditorOpener.php b/core/modules/media_library/src/MediaLibraryEditorOpener.php
index a31290fca60bb8c40e32e5d9e5b71e3e06e4675e..0c0af98b41970f92a8ebcd71269004c4c437553a 100644
--- a/core/modules/media_library/src/MediaLibraryEditorOpener.php
+++ b/core/modules/media_library/src/MediaLibraryEditorOpener.php
@@ -14,9 +14,7 @@
  * @see \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary
  *
  * @internal
- *   This is an internal part of the media system in Drupal core and may be
- *   subject to change in minor releases. This class should not be
- *   instantiated or extended by external code.
+ *   This service is an internal part of Media Library's CKEditor integration.
  */
 class MediaLibraryEditorOpener implements MediaLibraryOpenerInterface {
 
diff --git a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
index b34b8d308b9ce346d89ad21eb909a6daefbdd086..84099ed3f600f638066776a585e959f5a337d6c8 100644
--- a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
+++ b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
@@ -12,6 +12,9 @@
 
 /**
  * The media library opener for field widgets.
+ *
+ * @internal
+ *   This service is an internal part of Media Library's field widget.
  */
 class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {
 
diff --git a/core/modules/media_library/src/MediaLibraryState.php b/core/modules/media_library/src/MediaLibraryState.php
index 258f19ded14f5114e2a6dfe14aa6b62d5bda9eb3..e1fd6e2e8b9c8ef4a03add70974669ac6563d2d0 100644
--- a/core/modules/media_library/src/MediaLibraryState.php
+++ b/core/modules/media_library/src/MediaLibraryState.php
@@ -36,11 +36,6 @@
  * with them either.
  *
  * @see \Drupal\media_library\MediaLibraryOpenerInterface
- *
- * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
  */
 class MediaLibraryState extends ParameterBag {
 
diff --git a/core/modules/media_library/src/MediaLibraryUiBuilder.php b/core/modules/media_library/src/MediaLibraryUiBuilder.php
index b5e30c36f36e7f8ff7084f0192e79bc99cd4a1da..9c871a5be15e63c5332b4f6d6b0765d938bd33fc 100644
--- a/core/modules/media_library/src/MediaLibraryUiBuilder.php
+++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
@@ -17,9 +17,8 @@
  * Service which builds the media library.
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   This service is an internal part of the modal media library dialog and
+ *   does not provide any extension points.
  */
 class MediaLibraryUiBuilder {
 
@@ -123,11 +122,9 @@ public function buildUi(MediaLibraryState $state = NULL) {
     }
     else {
       return [
-        '#type' => 'html_tag',
-        '#tag' => 'div',
+        '#theme' => 'media_library_wrapper',
         '#attributes' => [
           'id' => 'media-library-wrapper',
-          'class' => ['media-library-wrapper'],
         ],
         'menu' => $this->buildMediaTypeMenu($state),
         'content' => $this->buildLibraryContent($state),
@@ -157,11 +154,12 @@ public function buildUi(MediaLibraryState $state = NULL) {
    */
   protected function buildLibraryContent(MediaLibraryState $state) {
     return [
-      '#type' => 'html_tag',
-      '#tag' => 'div',
+      '#type' => 'container',
+      '#theme_wrappers' => [
+        'container__media_library_content',
+      ],
       '#attributes' => [
         'id' => 'media-library-content',
-        'class' => ['media-library-content'],
       ],
       'form' => $this->buildMediaTypeAddForm($state),
       'view' => $this->buildMediaLibraryView($state),
@@ -234,10 +232,10 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
     // @todo: Add a class to the li element.
     //   https://www.drupal.org/project/drupal/issues/3029227
     $menu = [
-      '#theme' => 'links',
+      '#theme' => 'links__media_library_menu',
       '#links' => [],
       '#attributes' => [
-        'class' => ['media-library-menu', 'js-media-library-menu'],
+        'class' => ['js-media-library-menu'],
       ],
     ];
 
@@ -267,7 +265,6 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
           'query' => $link_state->all(),
         ]),
         'attributes' => [
-          'class' => ['media-library-menu__link'],
           'role' => 'button',
           'data-title' => $title,
         ],
diff --git a/core/modules/media_library/src/OpenerResolver.php b/core/modules/media_library/src/OpenerResolver.php
index 76a5acf6f7b777985a04595833e4051f70b1f66a..c164544aaedc89c419136e9533cca027ccee8cb0 100644
--- a/core/modules/media_library/src/OpenerResolver.php
+++ b/core/modules/media_library/src/OpenerResolver.php
@@ -11,6 +11,10 @@
  * services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
  * It is not an API and should not be extended or used by code that does not
  * interact with the Media Library module.
+ *
+ * @internal
+ *   This service is an internal part of the modal media library dialog and
+ *   does not provide any extension points or public API.
  */
 class OpenerResolver implements OpenerResolverInterface {
 
diff --git a/core/modules/media_library/src/OpenerResolverInterface.php b/core/modules/media_library/src/OpenerResolverInterface.php
index 7854c17d38fe13e8b756f4394056c29c0acfd1d9..4a877f68eccb8638fea46fbe743caad560f9f266 100644
--- a/core/modules/media_library/src/OpenerResolverInterface.php
+++ b/core/modules/media_library/src/OpenerResolverInterface.php
@@ -9,6 +9,11 @@
  * services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
  * It is not an API and should not be extended or used by code that does not
  * interact with the Media Library module.
+ *
+ * @internal
+ *   This interface is an internal part of the modal media library dialog and
+ *   is only implemented by \Drupal\media_library\OpenerResolver. It is not a
+ *   public API.
  */
 interface OpenerResolverInterface {
 
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index f4ca70e16c4a33514d4fda4bae61eab55c3b8257..5234c88d90db4afe2d3d68099421987086a5508d 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Field\WidgetBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\field_ui\FieldUI;
@@ -40,11 +41,9 @@
  * )
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   Plugin classes are internal.
  */
-class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
+class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface {
 
   /**
    * Entity type manager service.
@@ -317,11 +316,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#target_bundles' => isset($settings['target_bundles']) ? $settings['target_bundles'] : FALSE,
       '#attributes' => [
         'id' => $wrapper_id,
-        'class' => ['js-media-library-widget', 'media-library-widget'],
+        'class' => ['js-media-library-widget'],
+      ],
+      '#pre_render' => [
+        [$this, 'preRenderWidget'],
       ],
       '#attached' => [
         'library' => ['media_library/widget'],
       ],
+      '#theme_wrappers' => [
+        'fieldset__media_library_widget',
+      ],
     ];
 
     // When the list of allowed types in the field configuration is null,
@@ -337,26 +342,21 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     }
 
     if (empty($referenced_entities)) {
-      $element['empty_selection'] = [
-        '#type' => 'html_tag',
-        '#tag' => 'p',
-        '#value' => $this->t('No media items are selected.'),
-        '#attributes' => [
-          'class' => [
-            'media-library-widget-empty-text',
-          ],
-        ],
+      $element['#field_prefix']['empty_selection'] = [
+        '#markup' => $this->t('No media items are selected.'),
       ];
     }
     else {
-      $element['weight_toggle'] = [
+      // @todo Use a <button> link here, and delete
+      // seven_preprocess_fieldset__media_library_widget(), when
+      // https://www.drupal.org/project/drupal/issues/2999549 lands.
+      $element['#field_prefix']['weight_toggle'] = [
         '#type' => 'html_tag',
         '#tag' => 'button',
         '#value' => $this->t('Show media item weights'),
         '#attributes' => [
           'class' => [
             'link',
-            'media-library-widget__toggle-weight',
             'js-media-library-widget-toggle-weight',
           ],
         ],
@@ -365,21 +365,21 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
 
     $element['selection'] = [
       '#type' => 'container',
+      '#theme_wrappers' => [
+        'container__media_library_widget_selection',
+      ],
       '#attributes' => [
         'class' => [
           'js-media-library-selection',
-          'media-library-selection',
         ],
       ],
     ];
 
     foreach ($referenced_entities as $delta => $media_item) {
       $element['selection'][$delta] = [
-        '#type' => 'container',
+        '#theme' => 'media_library_item__widget',
         '#attributes' => [
           'class' => [
-            'media-library-item',
-            'media-library-item--grid',
             'js-media-library-item',
           ],
           // Add the tabindex '-1' to allow the focus to be shifted to the next
@@ -393,32 +393,28 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
           // @see ::updateWidget()
           'data-media-library-item-delta' => $delta,
         ],
-        'preview' => [
-          '#type' => 'container',
-          'remove_button' => [
-            '#type' => 'submit',
-            '#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
-            '#value' => $this->t('Remove'),
-            '#media_id' => $media_item->id(),
-            '#attributes' => [
-              'class' => ['media-library-item__remove'],
-              'aria-label' => $this->t('Remove @label', ['@label' => $media_item->label()]),
-            ],
-            '#ajax' => [
-              'callback' => [static::class, 'updateWidget'],
-              'wrapper' => $wrapper_id,
-              'progress' => [
-                'type' => 'throbber',
-                'message' => $this->t('Removing @label.', ['@label' => $media_item->label()]),
-              ],
+        'remove_button' => [
+          '#type' => 'submit',
+          '#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
+          '#value' => $this->t('Remove'),
+          '#media_id' => $media_item->id(),
+          '#attributes' => [
+            'aria-label' => $this->t('Remove @label', ['@label' => $media_item->label()]),
+          ],
+          '#ajax' => [
+            'callback' => [static::class, 'updateWidget'],
+            'wrapper' => $wrapper_id,
+            'progress' => [
+              'type' => 'throbber',
+              'message' => $this->t('Removing @label.', ['@label' => $media_item->label()]),
             ],
-            '#submit' => [[static::class, 'removeItem']],
-            // Prevent errors in other widgets from preventing removal.
-            '#limit_validation_errors' => $limit_validation_errors,
           ],
-          // @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
-          'rendered_entity' => $view_builder->view($media_item, 'media_library'),
+          '#submit' => [[static::class, 'removeItem']],
+          // Prevent errors in other widgets from preventing removal.
+          '#limit_validation_errors' => $limit_validation_errors,
         ],
+        // @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
+        'rendered_entity' => $view_builder->view($media_item, 'media_library'),
         'target_id' => [
           '#type' => 'hidden',
           '#value' => $media_item->id(),
@@ -426,12 +422,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
         // This hidden value can be toggled visible for accessibility.
         'weight' => [
           '#type' => 'number',
+          '#theme' => 'input__number__media_library_item_weight',
           '#title' => $this->t('Weight'),
           '#default_value' => $delta,
           '#attributes' => [
             'class' => [
               'js-media-library-item-weight',
-              'media-library-item__weight',
             ],
           ],
         ],
@@ -481,13 +477,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, $opener_parameters);
 
     // Add a button that will load the Media library in a modal using AJAX.
-    $element['media_library_open_button'] = [
+    $element['open_button'] = [
       '#type' => 'submit',
       '#value' => $this->t('Add media'),
       '#name' => $field_name . '-media-library-open-button' . $id_suffix,
       '#attributes' => [
         'class' => [
-          'media-library-open-button',
           'js-media-library-open-button',
         ],
         // The jQuery UI dialog automatically moves focus to the first :tabbable
@@ -514,8 +509,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     // JavaScript by adding the 'data-disabled-focus' attribute.
     // @see Drupal.behaviors.MediaLibraryWidgetDisableButton
     if (!$cardinality_unlimited && $remaining === 0) {
-      $element['media_library_open_button']['#attributes']['data-disabled-focus'] = 'true';
-      $element['media_library_open_button']['#attributes']['class'][] = 'visually-hidden';
+      $element['open_button']['#attributes']['data-disabled-focus'] = 'true';
+      $element['open_button']['#attributes']['class'][] = 'visually-hidden';
     }
 
     // This hidden field and button are used to add new items to the widget.
@@ -558,6 +553,32 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     return $element;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function trustedCallbacks() {
+    return ['preRenderWidget'];
+  }
+
+  /**
+   * Prepares the widget's render element for rendering.
+   *
+   * @param array $element
+   *   The element to transform.
+   *
+   * @return array
+   *   The transformed element.
+   *
+   * @see ::formElement()
+   */
+  public function preRenderWidget(array $element) {
+    if (isset($element['open_button'])) {
+      $element['#field_suffix']['open_button'] = $element['open_button'];
+      unset($element['open_button']);
+    }
+    return $element;
+  }
+
   /**
    * Gets the message to display when there are no allowed media types.
    *
@@ -633,7 +654,7 @@ public static function updateWidget(array $form, FormStateInterface $form_state)
     // This callback is either invoked from the remove button or the update
     // button, which have different nesting levels.
     $is_remove_button = end($triggering_element['#parents']) === 'remove_button';
-    $length = $is_remove_button ? -4 : -1;
+    $length = $is_remove_button ? -3 : -1;
     if (count($triggering_element['#array_parents']) < abs($length)) {
       throw new \LogicException('The element that triggered the widget update was at an unexpected depth. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
     }
@@ -690,7 +711,7 @@ public static function updateWidget(array $form, FormStateInterface $form_state)
     // 'data-disabled-focus' attribute and we also don't want to set the focus
     // here.
     // @see Drupal.behaviors.MediaLibraryWidgetDisableButton
-    elseif ($removed_last || (!$is_remove_button && !isset($element['media_library_open_button']['#attributes']['data-disabled-focus']))) {
+    elseif ($removed_last || (!$is_remove_button && !isset($element['open_button']['#attributes']['data-disabled-focus']))) {
       $response->addCommand(new InvokeCommand("#$wrapper_id .js-media-library-open-button", 'focus'));
     }
 
@@ -712,7 +733,7 @@ public static function removeItem(array $form, FormStateInterface $form_state) {
     if (count($triggering_element['#array_parents']) < 4) {
       throw new \LogicException('Expected the remove button to be more than four levels deep in the form. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
     }
-    $parents = array_slice($triggering_element['#array_parents'], 0, -4);
+    $parents = array_slice($triggering_element['#array_parents'], 0, -3);
     $element = NestedArray::getValue($form, $parents);
 
     // Get the field state.
@@ -721,7 +742,7 @@ public static function removeItem(array $form, FormStateInterface $form_state) {
     $field_state = static::getFieldState($element, $form_state);
 
     // Get the delta of the item being removed.
-    $delta = array_slice($triggering_element['#array_parents'], -3, 1)[0];
+    $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
     if (isset($values['selection'][$delta])) {
       // Add the weight of the removed item to the field state so we can shift
       // focus to the next/previous item in an easy way.
diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
index 232732a38a4beef6c61b706b9721513c19d4663c..2d0702bd4dcb5e00808c24808235125b3082d51c 100644
--- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
+++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
@@ -18,9 +18,7 @@
  * @ViewsField("media_library_select_form")
  *
  * @internal
- *   Media Library is an experimental module and its internal code may be
- *   subject to change in minor releases. External code should not instantiate
- *   or extend this class.
+ *   Plugin classes are internal.
  */
 class MediaLibrarySelectForm extends FieldPluginBase {
 
@@ -47,9 +45,7 @@ public function render(ResultRow $values) {
    *   The current state of the form.
    */
   public function viewsForm(array &$form, FormStateInterface $form_state) {
-    $form['#attributes'] = [
-      'class' => ['media-library-views-form', 'js-media-library-views-form'],
-    ];
+    $form['#attributes']['class'] = ['js-media-library-views-form'];
 
     // Add an attribute that identifies the media type displayed in the form.
     if (isset($this->view->args[0])) {
@@ -107,10 +103,7 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
     // the opener, and the opener should be responsible for moving the focus. An
     // example of this can be seen in MediaLibraryWidget::updateWidget().
     // @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::updateWidget()
-    $form['actions']['submit']['#attributes'] = [
-      'class' => ['media-library-select'],
-      'data-disable-refocus' => 'true',
-    ];
+    $form['actions']['submit']['#attributes']['data-disable-refocus'] = 'true';
   }
 
   /**
diff --git a/core/modules/media_library/src/Routing/RouteSubscriber.php b/core/modules/media_library/src/Routing/RouteSubscriber.php
index b0f4097a6b70dedddf9317e923fe0fff6375a3ef..e0de34edd4dc9092ab1b72814340282b12e0d7f2 100644
--- a/core/modules/media_library/src/Routing/RouteSubscriber.php
+++ b/core/modules/media_library/src/Routing/RouteSubscriber.php
@@ -7,6 +7,9 @@
 
 /**
  * Subscriber for media library routes.
+ *
+ * @internal
+ *   Tagged services are internal.
  */
 class RouteSubscriber extends RouteSubscriberBase {
 
diff --git a/core/modules/media_library/templates/media--media-library.html.twig b/core/modules/media_library/templates/media--media-library.html.twig
index 9e5e0dfc50a27ace87908bad6101f4c13742a6bc..6262e7d5062efd4fd0fad7a4676f2e4caa78acce 100644
--- a/core/modules/media_library/templates/media--media-library.html.twig
+++ b/core/modules/media_library/templates/media--media-library.html.twig
@@ -29,22 +29,21 @@
  * - status: Whether or not the Media is published.
  *
  * @see template_preprocess_media()
+ * @see media_library_preprocess_media()
  *
  * @ingroup themeable
  */
 #}
 <article{{ attributes }}>
   {% if content %}
-    <div{{ preview_attributes }}>
+    <div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
       {{ content|without('name') }}
     </div>
     {% if not status %}
-      <div class="media-library-item__status">{{ "unpublished" | t }}</div>
+      {{ "unpublished" | t }}
     {% endif %}
     <div{{ metadata_attributes }}>
-      <div class="media-library-item__name">
-        {{ name }}
-      </div>
+      {{ name }}
     </div>
   {% endif %}
 </article>
diff --git a/core/modules/media_library/templates/media-library-item.html.twig b/core/modules/media_library/templates/media-library-item.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..a765d04c11be9e7095c95289c71a8441dd2227a4
--- /dev/null
+++ b/core/modules/media_library/templates/media-library-item.html.twig
@@ -0,0 +1,22 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a media library item.
+ *
+ * This is used when displaying selected media items, either in the field
+ * widget or in the "Additional selected media" area when adding new
+ * media items in the media library modal dialog.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - content: The content of the media library item, plus any additional
+ *   fields or elements surrounding it.
+ *
+ * @see template_preprocess_media_library_item()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {{ content }}
+</div>
diff --git a/core/modules/media_library/templates/media-library-wrapper.html.twig b/core/modules/media_library/templates/media-library-wrapper.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..344637cd62c27b3a5d8a5ebb2394a0d26eaaaf1f
--- /dev/null
+++ b/core/modules/media_library/templates/media-library-wrapper.html.twig
@@ -0,0 +1,21 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a container used to wrap the media library's
+ * modal dialog interface.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - menu: The menu of availble media types to choose from.
+ * - content: The form to add new media items, followed by the grid or table of
+ *   existing media items to choose from.
+ *
+ * @see template_preprocess_media_library_wrapper()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {{ menu }}
+  {{ content }}
+</div>
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
index 5f9fb1df3b8f2f5a19e7be4e9e9e1fd2600156b7..5604fa22d20cb6456d66da43ab0a0cce7cb217ab 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -128,16 +128,16 @@ public function testAdministrationPage() {
     // Test that users can filter by type.
     $page->selectFieldOption('Media type', 'Type One');
     $page->pressButton('Apply filters');
-    $this->waitForText('Dog');
     $this->waitForNoText('Turtle');
+    $assert_session->pageTextContains('Dog');
     $page->selectFieldOption('Media type', 'Type Two');
     $page->pressButton('Apply filters');
-    $this->waitForNoText('Dog');
     $this->waitForText('Turtle');
+    $assert_session->pageTextNotContains('Dog');
 
     // Test that selecting elements as a part of bulk operations works.
     $page->selectFieldOption('Media type', '- Any -');
-    $page->pressButton('Apply filters');
+    $assert_session->elementExists('css', '#views-exposed-form-media-library-page')->submit();
     $this->waitForText('Dog');
 
     // This tests that anchor tags clicked inside the preview are suppressed.
@@ -145,14 +145,17 @@ public function testAdministrationPage() {
     $this->submitForm([], 'Apply to selected items');
     $assert_session->pageTextContains('Dog');
     $assert_session->pageTextNotContains('Cat');
-    $this->submitForm([], 'Delete');
+    // For reasons that are not clear, deleting media items by pressing the
+    // "Delete" button can fail (the button is found, but never actually pressed
+    // by the Mink driver). This workaround allows the delete form to be
+    // submitted.
+    $assert_session->elementExists('css', 'form')->submit();
     $assert_session->pageTextNotContains('Dog');
     $assert_session->pageTextContains('Cat');
 
     // Test the 'Select all media' checkbox and assert that it makes the
     // expected announcements.
-    $select_all = $assert_session->waitForField('Select all media');
-    $this->assertNotEmpty($select_all);
+    $select_all = $this->waitForFieldExists('Select all media');
     $select_all->check();
     $this->waitForText('All 7 items selected');
     $select_all->uncheck();
@@ -160,7 +163,11 @@ public function testAdministrationPage() {
     $select_all->check();
     $page->selectFieldOption('Action', 'media_delete_action');
     $this->submitForm([], 'Apply to selected items');
-    $page->pressButton('Delete');
+    // For reasons that are not clear, deleting media items by pressing the
+    // "Delete" button can fail (the button is found, but never actually pressed
+    // by the Mink driver). This workaround allows the delete form to be
+    // submitted.
+    $assert_session->elementExists('css', 'form')->submit();
 
     $assert_session->pageTextNotContains('Cat');
     $assert_session->pageTextNotContains('Turtle');
@@ -439,7 +446,6 @@ public function testWidget() {
 
     // Assert generic media library elements.
     $this->openMediaLibraryForField('field_unlimited_media');
-    $this->assertFalse($assert_session->elementExists('css', '.media-library-select-all')->isVisible());
     $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
 
     // Assert that the media type menu is available when more than 1 type is
@@ -984,13 +990,11 @@ public function testWidgetUpload() {
     $assert_session->fieldExists('Add files');
 
     // Assert we can upload a file to the default tab type_three.
-    $assert_session->elementExists('css', '.media-library-add-form--without-input');
-    $assert_session->elementNotExists('css', '.media-library-add-form--with-input');
+    $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
     $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
     $this->assertMediaAdded();
-    $assert_session->elementExists('css', '.media-library-add-form--with-input');
-    $assert_session->elementNotExists('css', '.media-library-add-form--without-input');
-    // We do not have a pre-selected items, so the container should not be added
+    $assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
+    // We do not have pre-selected items, so the container should not be added
     // to the form.
     $assert_session->pageTextNotContains('Additional selected media');
     // Files are temporary until the form is saved.
@@ -1326,12 +1330,10 @@ public function testWidgetUploadAdvancedUi() {
     $assert_session->fieldExists('Add files');
 
     // Assert we can upload a file to the default tab type_three.
-    $assert_session->elementExists('css', '.media-library-add-form--without-input');
-    $assert_session->elementNotExists('css', '.media-library-add-form--with-input');
+    $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
     $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
     $this->assertMediaAdded();
-    $assert_session->elementExists('css', '.media-library-add-form--with-input');
-    $assert_session->elementNotExists('css', '.media-library-add-form--without-input');
+    $assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
     // We do not have a pre-selected items, so the container should not be added
     // to the form.
     $assert_session->elementNotExists('css', 'details summary:contains(Additional selected media)');
@@ -2247,6 +2249,9 @@ protected function openMediaLibraryForField($field_name, $after_open_selector =
     $assert_session->linkExists('Grid');
     $assert_session->linkExists('Table');
 
+    // The "select all" checkbox should never be present in the modal.
+    $assert_session->elementNotExists('css', '.media-library-select-all');
+
     return $this->assertElementExistsAfterWait('css', $after_open_selector);
   }
 
@@ -2379,18 +2384,16 @@ protected function switchToMediaLibraryTable() {
    * Asserts that the grid display of the widget view is visible.
    */
   protected function assertMediaLibraryGrid() {
-    $assert_session = $this->assertSession();
-    $assert_session->elementExists('css', '.view-media-library.view-display-id-widget');
-    $assert_session->elementNotExists('css', '.view-media-library.view-display-id-widget_table');
+    $this->assertSession()
+      ->elementExists('css', '.js-media-library-view[data-view-display-id="widget"]');
   }
 
   /**
    * Asserts that the table display of the widget view is visible.
    */
   protected function assertMediaLibraryTable() {
-    $assert_session = $this->assertSession();
-    $assert_session->elementExists('css', '.view-media-library.view-display-id-widget_table');
-    $assert_session->elementNotExists('css', '.view-media-library.view-display-id-widget');
+    $this->assertSession()
+      ->elementExists('css', '.js-media-library-view[data-view-display-id="widget_table"]');
   }
 
   /**
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.audio.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_form_display.media.audio.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.document.media_library.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.document.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_form_display.media.document.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_form_display.media.document.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.image.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_form_display.media.image.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.remote_video.media_library.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.remote_video.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_form_display.media.remote_video.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_form_display.media.remote_video.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.video.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_form_display.media.video.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_view_display.media.audio.media_library.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.audio.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_view_display.media.audio.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_view_display.media.audio.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_view_display.media.document.media_library.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.document.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_view_display.media.document.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_view_display.media.document.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_view_display.media.image.media_library.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.image.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_view_display.media.image.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_view_display.media.image.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_view_display.media.remote_video.media_library.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_view_display.media.remote_video.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_view_display.media.remote_video.media_library.yml
diff --git a/core/modules/media_library/config/optional/core.entity_view_display.media.video.media_library.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.video.media_library.yml
similarity index 100%
rename from core/modules/media_library/config/optional/core.entity_view_display.media.video.media_library.yml
rename to core/profiles/standard/config/optional/core.entity_view_display.media.video.media_library.yml
diff --git a/core/themes/classy/classy.info.yml b/core/themes/classy/classy.info.yml
index ce192b6a0b95efeb9c1cb21617a0c99ad9e21c7c..3f39981664af117ddbc9e138c56f316e4c57b22e 100644
--- a/core/themes/classy/classy.info.yml
+++ b/core/themes/classy/classy.info.yml
@@ -24,6 +24,10 @@ libraries-extend:
     - classy/progress
   media/media_embed_ckeditor_theme:
     - classy/media_embed_ckeditor_theme
+  media_library/view:
+    - classy/media_library
+  media_library/widget:
+    - classy/media_library
 
 ckeditor_stylesheets:
   - css/components/media-embed-error.css
diff --git a/core/themes/classy/classy.libraries.yml b/core/themes/classy/classy.libraries.yml
index 975b64283a9ee3ba064bf07e3766b517a9a8e8df..872b9738e4e6ef975ca9fd51bc088b5efba47765 100644
--- a/core/themes/classy/classy.libraries.yml
+++ b/core/themes/classy/classy.libraries.yml
@@ -68,6 +68,12 @@ indented:
     component:
       css/components/indented.css: {}
 
+media_library:
+  version: VERSION
+  css:
+    layout:
+      css/layout/media-library.css: {}
+
 messages:
   version: VERSION
   css:
diff --git a/core/themes/classy/classy.theme b/core/themes/classy/classy.theme
new file mode 100644
index 0000000000000000000000000000000000000000..4407c9a528249b2c27c54868f21c4c1b806b7c89
--- /dev/null
+++ b/core/themes/classy/classy.theme
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Functions to support theming in the Classy theme.
+ */
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\Form\ViewsForm;
+
+/**
+ * Implements hook_preprocess_links__media_library_menu().
+ *
+ * This targets the menu of available media types in the media library's modal
+ * dialog.
+ *
+ * @todo Do this in the relevant template once
+ *   https://www.drupal.org/project/drupal/issues/3088856 is resolved.
+ */
+function classy_preprocess_links__media_library_menu(array &$variables) {
+  foreach ($variables['links'] as &$link) {
+    $link['link']['#options']['attributes']['class'][] = 'media-library-menu__link';
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function classy_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
+  $form_object = $form_state->getFormObject();
+  if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
+    $form['#attributes']['class'][] = 'media-library-views-form';
+  }
+}
diff --git a/core/themes/classy/css/layout/media-library.css b/core/themes/classy/css/layout/media-library.css
new file mode 100644
index 0000000000000000000000000000000000000000..84dee10daa5407063fa77cae7cdbae9ca37ea2e5
--- /dev/null
+++ b/core/themes/classy/css/layout/media-library.css
@@ -0,0 +1,28 @@
+/**
+ * @file
+ * Contains minimal layout styling for the media library.
+ */
+
+.media-library-wrapper {
+  display: flex;
+}
+
+.media-library-menu {
+  flex-basis: 20%;
+  flex-shrink: 0;
+}
+
+.media-library-content {
+  flex-grow: 1;
+}
+
+.media-library-views-form {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.media-library-views-form .media-library-item {
+  justify-content: space-between;
+  max-width: 23%;
+  margin: 1%;
+}
diff --git a/core/themes/classy/templates/media-library/container--media-library-content.html.twig b/core/themes/classy/templates/media-library/container--media-library-content.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..7c930e2c7b64980f3b1ea8413d143f2791f888b7
--- /dev/null
+++ b/core/themes/classy/templates/media-library/container--media-library-content.html.twig
@@ -0,0 +1,28 @@
+{#
+/**
+ * @file
+ * Theme implementation the content area of the modal media library dialog.
+ *
+ * The content area is everything that is not the menu of available media
+ * types. This includes the form to add new media items, if available, and
+ * the view of available media to select.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - children: The rendered child elements of the container.
+ * - has_parent: A flag to indicate that the container has one or more parent
+     containers.
+ *
+ * @see template_preprocess_container()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set classes = [
+    has_parent ? 'js-form-wrapper',
+    has_parent ? 'form-wrapper',
+    'media-library-content',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>{{ children }}</div>
diff --git a/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig b/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..7c0af44307f6ac054143fd886c80bdff1c84fbc1
--- /dev/null
+++ b/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig
@@ -0,0 +1,28 @@
+{#
+/**
+ * @file
+ * Theme implementation of a wrapper for selected media items.
+ *
+ * This is used to wrap around the set of media items that are currently
+ * selected in the media library widget (not the modal dialog), which may
+ * be used for entity reference fields that target media.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - children: The rendered child elements of the container.
+ * - has_parent: A flag to indicate that the container has one or more parent
+     containers.
+ *
+ * @see template_preprocess_container()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set classes = [
+    has_parent ? 'js-form-wrapper',
+    has_parent ? 'form-wrapper',
+    'media-library-selection',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>{{ children }}</div>
diff --git a/core/themes/classy/templates/media-library/links--media-library-menu.html.twig b/core/themes/classy/templates/media-library/links--media-library-menu.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..dfc80f50f0cb4e51e7eb38deb5cb8eb3dee56a9e
--- /dev/null
+++ b/core/themes/classy/templates/media-library/links--media-library-menu.html.twig
@@ -0,0 +1,36 @@
+{% extends "links.html.twig" %}
+{#
+/**
+ * @file
+ * Theme implementation of the media type menu in the media library dialog.
+ *
+ * Available variables:
+ * - attributes: Attributes for the UL containing the list of links.
+ * - links: Links to be output.
+ *   Each link will have the following elements:
+ *   - title: The link text.
+ *   - href: The link URL. If omitted, the 'title' is shown as a plain text
+ *     item in the links list. If 'href' is supplied, the entire link is passed
+ *     to l() as its $options parameter.
+ *   - attributes: (optional) HTML attributes for the anchor, or for the <span>
+ *     tag if no 'href' is supplied.
+ * - heading: (optional) A heading to precede the links.
+ *   - text: The heading text.
+ *   - level: The heading level (e.g. 'h2', 'h3').
+ *   - attributes: (optional) A keyed list of attributes for the heading.
+ *   If the heading is a string, it will be used as the text of the heading and
+ *   the level will default to 'h2'.
+ *
+ *   Headings should be used on navigation menus and any list of links that
+ *   consistently appears on multiple pages. To make the heading invisible use
+ *   the 'visually-hidden' CSS class. Do not use 'display:none', which
+ *   removes it from screen readers and assistive technology. Headings allow
+ *   screen reader and keyboard only users to navigate to or skip the links.
+ *   See http://juicystudio.com/article/screen-readers-display-none.php and
+ *   http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
+ *
+ * @see classy_preprocess_links__media_library_menu()
+ * @see template_preprocess_links()
+ */
+#}
+{% set attributes = attributes.addClass('media-library-menu') %}
diff --git a/core/themes/classy/templates/media-library/media--media-library.html.twig b/core/themes/classy/templates/media-library/media--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..e88635424fc07f47e181e5e894427f9e931c7bc8
--- /dev/null
+++ b/core/themes/classy/templates/media-library/media--media-library.html.twig
@@ -0,0 +1,55 @@
+{#
+/**
+ * @file
+ * Theme override of a media item in the media library.
+ *
+ * This is used for media that the user can select from the grid of media
+ * items. It is not used for items that have already been selected in the
+ * corresponding field widget, or for items that have been previously selected
+ * before adding new media to the library.
+ *
+ * Available variables:
+ * - media: The entity with limited access to object properties and methods.
+ *   Only method names starting with "get", "has", or "is" and a few common
+ *   methods such as "id", "label", and "bundle" are available. For example:
+ *   - entity.getEntityTypeId() will return the entity type ID.
+ *   - entity.hasField('field_example') returns TRUE if the entity includes
+ *     field_example. (This does not indicate the presence of a value in this
+ *     field.)
+ *   Calling other methods, such as entity.delete(), will result in an exception.
+ *   See \Drupal\Core\Entity\EntityInterface for a full list of methods.
+ * - name: Name of the media.
+ * - content: Media content.
+ * - title_prefix: Additional output populated by modules, intended to be
+ *   displayed in front of the main title tag that appears in the template.
+ * - title_suffix: Additional output populated by modules, intended to be
+ *   displayed after the main title tag that appears in the template.
+ * - view_mode: View mode; for example, "teaser" or "full".
+ * - attributes: HTML attributes for the containing element.
+ * - title_attributes: Same as attributes, except applied to the main title
+ *   tag that appears in the template.
+ * - url: Direct URL of the media.
+ * - preview_attributes: HTML attributes for the preview wrapper.
+ * - metadata_attributes: HTML attributes for the expandable metadata area.
+ * - status: Whether or not the Media is published.
+ *
+ * @see template_preprocess_media()
+ *
+ * @ingroup themeable
+ */
+#}
+<article{{ attributes }}>
+  {% if content %}
+    <div{{ preview_attributes.addClass('media-library-item__preview js-media-library-item-preview') }}>
+      {{ content|without('name') }}
+    </div>
+    {% if not status %}
+      <div class="media-library-item__status">{{ "unpublished" | t }}</div>
+    {% endif %}
+    <div{{ metadata_attributes.addClass('media-library-item__attributes') }}>
+      <div class="media-library-item__name">
+        {{ name }}
+      </div>
+    </div>
+  {% endif %}
+</article>
diff --git a/core/themes/classy/templates/media-library/media-library-item--small.html.twig b/core/themes/classy/templates/media-library/media-library-item--small.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..ba03858b7f82cd64b985264cc9f120bd80db6038
--- /dev/null
+++ b/core/themes/classy/templates/media-library/media-library-item--small.html.twig
@@ -0,0 +1,31 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a media library item.
+ *
+ * This is used when displaying selected media items, either in the field
+ * widget or in the "Additional selected media" area when adding new
+ * media items in the media library modal dialog.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - content: The content of the media library item, plus any additional
+ *   fields or elements surrounding it.
+ *
+ * @see seven_preprocess_media_library_item__small()
+ * @see seven_preprocess_media_library_item__widget()
+ * @see template_preprocess_media_library_item()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set classes = [
+    'media-library-item',
+    'media-library-item--grid',
+    'media-library-item--small',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
+  {{ content }}
+</div>
diff --git a/core/themes/classy/templates/media-library/media-library-item.html.twig b/core/themes/classy/templates/media-library/media-library-item.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..297780e0f736548762e4bef93aa85e98c84b9142
--- /dev/null
+++ b/core/themes/classy/templates/media-library/media-library-item.html.twig
@@ -0,0 +1,28 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a media library item.
+ *
+ * This is used when displaying selected media items, either in the field
+ * widget or in the "Additional selected media" area when adding new
+ * media items in the media library modal dialog.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - content: The content of the media library item, plus any additional
+ *   fields or elements surrounding it.
+ *
+ * @see template_preprocess_media_library_item()
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+  set classes = [
+    'media-library-item',
+    'media-library-item--grid',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
+  {{ content }}
+</div>
diff --git a/core/themes/classy/templates/media-library/media-library-wrapper.html.twig b/core/themes/classy/templates/media-library/media-library-wrapper.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..850f63aa1749afd0ba8e7d4dc00c1e41d8bffb9b
--- /dev/null
+++ b/core/themes/classy/templates/media-library/media-library-wrapper.html.twig
@@ -0,0 +1,21 @@
+{#
+/**
+ * @file
+ * Theme override of a container used to wrap the media library's modal dialog
+ * interface.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - menu: The menu of availble media types to choose from.
+ * - content: The form to add new media items, followed by the grid or table of
+ *   existing media items to choose from.
+ *
+ * @see template_preprocess_media_library_wrapper()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes.addClass('media-library-wrapper') }}>
+  {{ menu }}
+  {{ content }}
+</div>
diff --git a/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig b/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..a94d4e2b636dd65a0bbf0aa27e8c11eb06935383
--- /dev/null
+++ b/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig
@@ -0,0 +1,35 @@
+{#
+/**
+ * @file
+ * Theme override of the media library view.
+ *
+ * This is used to display a grid of media items, in both the administrative
+ * interface and in the modal media library dialog's grid layout.
+ *
+ * Available variables:
+ * - title: The title of this group of rows. May be empty.
+ * - rows: A list of the view's row items.
+ *   - attributes: The row's HTML attributes.
+ *   - content: The row's content.
+ * - view: The view object.
+ * - default_row_class: A flag indicating whether default classes should be
+ *   used on rows.
+ *
+ * @see template_preprocess_views_view_unformatted()
+ */
+#}
+{% if title %}
+  <h3>{{ title }}</h3>
+{% endif %}
+{% for row in rows %}
+  {%
+    set row_classes = [
+      default_row_class ? 'views-row',
+      'media-library-item',
+      'media-library-item--grid',
+    ]
+  %}
+  <div{{ row.attributes.addClass(row_classes) }}>
+    {{- row.content -}}
+  </div>
+{% endfor %}
diff --git a/core/modules/media_library/css/media_library.theme.css b/core/themes/seven/css/theme/media-library.css
similarity index 83%
rename from core/modules/media_library/css/media_library.theme.css
rename to core/themes/seven/css/theme/media-library.css
index 0b698ffee3f46aebed989fa65bee97a06b5401fc..8c75fda49ff730ae49019e294779b114b1ace396 100644
--- a/core/modules/media_library/css/media_library.theme.css
+++ b/core/themes/seven/css/theme/media-library.css
@@ -1,11 +1,11 @@
 /**
- * @file media_library.theme.css
- *
- * @todo Move into the Seven theme when this module is marked as stable.
- * @see https://www.drupal.org/project/drupal/issues/2980769
+ * @file media-library.css
+
+ * Styling for Media Library.
  */
 
 .media-library-wrapper {
+  display: flex;
   margin: -1em;
 }
 
@@ -33,6 +33,8 @@
  */
 .media-library-menu li {
   display: block;
+  padding: 0;
+  list-style: none;
 }
 
 .media-library-menu__link {
@@ -91,12 +93,14 @@
 }
 
 /**
- * Remove outline from added media container.
+ * Remove outline from added media list.
  *
- * The added media container receives focus after adding new media, but since
- * it is not an interactive element it does not need an outline.
+ * The added media list receives focus after adding new media, but since it is
+ * not an interactive element, it does not need an outline.
  */
 .media-library-add-form__added-media {
+  margin: 0;
+  padding: 0;
   outline: none;
 }
 
@@ -116,10 +120,6 @@
   margin: 8px 0 0;
 }
 
-.media-library-add-form__description {
-  margin: 0;
-}
-
 /* Style the media add oEmbed form. */
 .media-library-add-form--oembed .media-library-add-form__input-wrapper {
   display: flex;
@@ -163,21 +163,56 @@
 
 /* Generic media library view styles. */
 .media-library-select-all {
-  margin: 10px 0 10px 0;
+  flex-basis: 100%;
+  width: 100%;
+  margin: 10px 8px;
 }
-
 .media-library-select-all input {
   margin-right: 10px;
 }
+[dir="rtl"] .media-library-select-all input {
+  margin-left: 10px;
+}
+
+.media-library-views-form,
+.media-library-selection,
+.media-library-add-form__selected-media .details-wrapper,
+.media-library-views-form__bulk_form,
+.media-library-view .form--inline {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.media-library-views-form > .form-actions {
+  flex-basis: 100%;
+}
+
+.media-library-views-form__header {
+  flex-basis: 100%;
+}
 
 .media-library-views-form__header .form-item {
   margin-right: 8px;
 }
 
+.media-library-views-form__rows {
+  display: flex;
+  flex-wrap: wrap;
+  flex-basis: 100%;
+  margin: 0 -8px;
+}
+
 .media-library-view .form-actions {
+  align-self: flex-end;
   margin: 0.75em 0;
 }
 
+@media screen and (max-width: 600px) {
+  .media-library-view .form-actions {
+    flex-basis: 100%;
+  }
+}
+
 .media-library-view .media-library-view--form-actions {
   clear: left;
   align-self: flex-end;
@@ -203,13 +238,8 @@
   justify-content: space-between;
 }
 
-/**
- * @todo Remove order and reorder the views header and filters via a views
- *   template in https://www.drupal.org/project/drupal/issues/3035994
- */
 .media-library-wrapper .view-header {
   align-self: flex-end;
-  order: 2;
   margin: 1em 0;
   text-align: right; /* LTR */
 }
@@ -217,29 +247,8 @@
   text-align: left;
 }
 
-/**
- * @todo Remove order and reorder the views header and filters via a views
- *   template in https://www.drupal.org/project/drupal/issues/3035994
- */
-.media-library-wrapper .media-library-view .view-filters {
-  order: 1;
-}
-
-/**
- * @todo Remove order and reorder the views header and filters via a views
- *   template in https://www.drupal.org/project/drupal/issues/3035994
- */
 .media-library-wrapper .media-library-view .view-content {
   flex: 0 0 100%;
-  order: 3;
-}
-
-/**
- * @todo Remove order and reorder the views header and filters via a views
- *   template in https://www.drupal.org/project/drupal/issues/3035994
- */
-.media-library-wrapper .media-library-view .pager {
-  order: 4;
 }
 
 .media-library-wrapper .views-display-link {
@@ -260,46 +269,31 @@
 
 .media-library-wrapper .views-display-link-widget {
   margin-right: 15px;
-  background: url(../../../misc/icons/333333/grid.svg) left 0 no-repeat; /* LTR */
+  background: url(../../../../misc/icons/333333/grid.svg) left 0 no-repeat; /* LTR */
 }
 [dir="rtl"] .media-library-wrapper .views-display-link-widget {
   background-position: right 0;
 }
 
 .media-library-wrapper .views-display-link-widget_table {
-  background: url(../../../misc/icons/333333/table.svg) left 0 no-repeat; /* LTR */
+  background: url(../../../../misc/icons/333333/table.svg) left 0 no-repeat; /* LTR */
 }
 [dir="rtl"] .media-library-wrapper .views-display-link-widget_table {
   background-position: right 0;
 }
 
-/* Media library item grid styles. */
-.media-library-views-form {
-  margin: 0 -8px;
-}
-
 /**
- * Fix the negative margin of the grid.
- *
- * We need to fix the negative margin of the grid for table based displays and
- * form elements that should not be part of the grid.
- *
- * @todo: Remove when new wrapper is added to apply negative margins in
- *    https://www.drupal.org/project/drupal/issues/3038489
+ * Style the media library grid items.
  */
-.media-library-views-form__header,
-.media-library-select-all,
-.media-library-views-form > .views-table {
-  margin: 0 8px;
+.media-library-item {
+  position: relative;
 }
 
 /**
- * Style the media library grid items.
- *
- * The media library item container receives screen reader focus when items are
- * removed. Since it is not an interactive element, it does not need an
- * outline.
- */
+* The media library item container receives screen reader focus when items are
+* removed. Since it is not an interactive element, it does not need an
+* outline.
+*/
 .media-library-item--grid {
   justify-content: center;
   box-sizing: border-box;
@@ -332,6 +326,23 @@
   width: 33.3%;
 }
 
+.media-library-widget-modal .ui-dialog-buttonpane {
+  display: flex;
+  align-items: center;
+}
+
+.media-library-widget-modal .ui-dialog-buttonpane .form-actions {
+  flex: 1;
+}
+
+/**
+ * By default, the dialog is too narrow to be usable.
+ * @see Drupal.ckeditor.openDialog()
+ */
+.ui-dialog--narrow.media-library-widget-modal {
+  max-width: 75%;
+}
+
 @media screen and (min-width: 45em) {
   .media-library-item--grid {
     width: 33.3%;
@@ -409,6 +420,18 @@
   border-color: #0076c0;
 }
 
+.media-library-item__click-to-select-checkbox {
+  position: absolute;
+  z-index: 1;
+  top: 16px;
+  left: 16px; /* LTR */
+  display: block;
+}
+[dir="rtl"] .media-library-item__click-to-select-checkbox {
+  right: 16px;
+  left: auto;
+}
+
 .media-library-item__click-to-select-checkbox input {
   width: 20px;
   height: 20px;
@@ -418,6 +441,12 @@
   margin: 0;
 }
 
+.media-library-item__click-to-select-trigger {
+  overflow: hidden;
+  height: 100%;
+  cursor: pointer;
+}
+
 /* Media library item table styles. */
 .media-library-item--table img {
   max-width: 100px;
@@ -427,15 +456,24 @@
 /* Media library entity view display styles. */
 .media-library-item__preview {
   padding-bottom: 34px;
+  cursor: move;
 }
 
 .media-library-item__status {
+  position: absolute;
+  top: 40px;
+  left: 5px; /* LTR */
   padding: 5px 10px;
+  pointer-events: none;
   color: #e4e4e4;
   background: #666;
   font-size: 12px;
   font-style: italic;
 }
+[dir="rtl"] .media-library-item__status {
+  right: 5px;
+  left: auto;
+}
 
 .media-library-item__attributes {
   position: absolute;
@@ -472,6 +510,7 @@
 }
 
 .media-library-item--disabled {
+  pointer-events: none;
   opacity: 0.5;
 }
 
@@ -560,7 +599,7 @@
 }
 
 .media-library-item__edit {
-  background: url("../../../misc/icons/787878/pencil.svg") #fff center no-repeat;
+  background: url("../../../../misc/icons/787878/pencil.svg") #fff center no-repeat;
   background-size: 13px;
 }
 .media-library-item__remove,
@@ -570,7 +609,7 @@
 .media-library-item__remove.button:disabled:active,
 .media-library-item__remove.button:hover,
 .media-library-item__remove.button:focus {
-  background: url("../../../misc/icons/787878/ex.svg") #fff center no-repeat;
+  background: url("../../../../misc/icons/787878/ex.svg") #fff center no-repeat;
   background-size: 13px;
 }
 .media-library-item__edit:hover,
@@ -656,7 +695,7 @@
   color: transparent;
   border: 0;
   border-radius: 0;
-  background: transparent url(../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
+  background: transparent url(../../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
   font-weight: normal;
   line-height: 16px;
 }
@@ -673,7 +712,7 @@
 .media-library-add-form__remove-button.button:focus {
   color: #787878;
   border: 0;
-  background: transparent url(../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
+  background: transparent url(../../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
 }
 [dir="rtl"] .media-library-add-form__remove-button:focus,
 [dir="rtl"] .media-library-add-form__remove-button.button:disabled,
@@ -686,10 +725,15 @@
 .media-library-add-form__remove-button.button:hover {
   color: #e00;
   border: 0;
-  background: transparent url(../../../misc/icons/ee0000/ex.svg) right 2px no-repeat; /* LTR */
+  background: transparent url(../../../../misc/icons/ee0000/ex.svg) right 2px no-repeat; /* LTR */
   box-shadow: none;
 }
 [dir="rtl"] .media-library-add-form__remove-button:hover,
 [dir="rtl"] .media-library-add-form__remove-button.button:hover {
   background-position: left 2px;
 }
+
+/* @todo Remove in https://www.drupal.org/project/drupal/issues/3064914 */
+.views-live-preview .media-library-view div.views-row + div.views-row {
+  margin-top: 0;
+}
diff --git a/core/themes/seven/seven.info.yml b/core/themes/seven/seven.info.yml
index f7172a0d1fb203e93cb9f171d398850c8a369ea4..1873ca6148be58dd39a616232907f73bf0b42330 100644
--- a/core/themes/seven/seven.info.yml
+++ b/core/themes/seven/seven.info.yml
@@ -45,6 +45,10 @@ libraries-override:
     css:
       component:
         css/components/details.css: false
+  classy/media_library:
+    css:
+      layout:
+        css/layout/media-library.css: false
 
 libraries-extend:
   core/ckeditor:
@@ -53,6 +57,10 @@ libraries-extend:
     - seven/vertical-tabs
   core/jquery.ui:
     - seven/seven.jquery.ui
+  media_library/view:
+    - seven/media_library
+  media_library/widget:
+    - seven/media_library
   tour/tour-styling:
     - seven/tour-styling
 quickedit_stylesheets:
diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml
index 651477efb20997590e2a5f716c08efc8fb4f9eb4..8db54e12dffb62b045da9a3ed911cd2682fc2633 100644
--- a/core/themes/seven/seven.libraries.yml
+++ b/core/themes/seven/seven.libraries.yml
@@ -129,6 +129,12 @@ media-form:
   dependencies:
     - media/form
 
+media_library:
+  version: VERSION
+  css:
+    theme:
+      css/theme/media-library.css: {}
+
 layout_builder_content_translation_admin:
   version: VERSION
   css:
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index 078d2adc8063117db4be7b60672ac08e5783598d..7908b0bddf7ea5ea1ec6d5a7625f9f39d927eed5 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -8,6 +8,8 @@
 use Drupal\Core\Url;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\media\MediaForm;
+use Drupal\views\Form\ViewsForm;
+use Drupal\views\ViewExecutable;
 
 /**
  * Implements hook_preprocess_HOOK() for HTML document templates.
@@ -186,3 +188,189 @@ function seven_form_media_form_alter(&$form, FormStateInterface $form_state) {
 function seven_form_language_content_settings_form_alter(array &$form, FormStateInterface $form_state) {
   $form['#attached']['library'][] = 'seven/layout_builder_content_translation_admin';
 }
+
+/**
+ * Implements hook_preprocess_views_view_fields().
+ *
+ * This targets each rendered media item in the grid display of the media
+ * library's modal dialog.
+ */
+function seven_preprocess_views_view_fields__media_library(array &$variables) {
+  // Add classes to media rendered entity field so it can be targeted for
+  // styling. Adding this class in a template is very difficult to do.
+  if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
+    $variables['fields']['rendered_entity']->wrapper_attributes->addClass('media-library-item__click-to-select-trigger');
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function seven_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
+  $form_object = $form_state->getFormObject();
+  if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
+    if (isset($form['header'])) {
+      $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
+      $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
+    }
+    $form['actions']['submit']['#attributes']['class'] = ['media-library-select'];
+  }
+  // Add after build to add a CSS class to the form actions.
+  if ($form_id === 'views_exposed_form' && strpos($form['#id'], 'views-exposed-form-media-library-widget') === 0) {
+    $form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
+  }
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ */
+function seven_form_media_library_add_form_alter(array &$form, FormStateInterface $form_state) {
+  $form['#attributes']['class'][] = 'media-library-add-form';
+  $form['#attached']['library'][] = 'seven/media_library';
+
+  // If there are unsaved media items, apply styling classes to various parts
+  // of the form.
+  if (isset($form['media'])) {
+    $form['#attributes']['class'][] = 'media-library-add-form--with-input';
+
+    // Put a wrapper around the informational message above the unsaved media
+    // items.
+    $form['description']['#template'] = '<p class="media-library-add-form__description">{{ text }}</p>';
+  }
+  else {
+    $form['#attributes']['class'][] = 'media-library-add-form--without-input';
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function seven_form_media_library_add_form_upload_alter(array &$form, FormStateInterface $form_state) {
+  $form['attributes']['class'][] = 'media-library-add-form--upload';
+
+  if (isset($form['container'])) {
+    $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function seven_form_media_library_add_form_oembed_alter(array &$form, FormStateInterface $form_state) {
+  $form['attributes']['class'][] = 'media-library-add-form--oembed';
+
+  // If no media items have been added yet, add a couple of styling classes
+  // to the initial URL form.
+  if (isset($form['container'])) {
+    $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
+    $form['container']['url']['#attributes']['class'][] = 'media-library-add-form-oembed-url';
+    $form['container']['submit']['#attributes']['class'][] = 'media-library-add-form-oembed-submit';
+  }
+}
+
+/**
+ * Implements hook_preprocess_item_list__media_library_add_form_media_list().
+ *
+ * This targets each new, unsaved media item added to the media library, before
+ * they are saved.
+ */
+function seven_preprocess_item_list__media_library_add_form_media_list(array &$variables) {
+  foreach ($variables['items'] as &$item) {
+    $item['value']['preview']['#attributes']['class'][] = 'media-library-add-form__preview';
+    $item['value']['fields']['#attributes']['class'][] = 'media-library-add-form__fields';
+    $item['value']['remove_button']['#attributes']['class'][] = 'media-library-add-form__remove-button';
+
+    // #source_field_name is set by AddFormBase::buildEntityFormElement()
+    // to help themes and form_alter hooks identify the source field.
+    $fields = &$item['value']['fields'];
+    $source_field_name = $fields['#source_field_name'];
+    if (isset($fields[$source_field_name])) {
+      $fields[$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
+    }
+  }
+}
+
+/**
+ * Implements hook_preprocess_media_library_item__widget().
+ *
+ * This targets each media item selected in an entity reference field.
+ */
+function seven_preprocess_media_library_item__widget(array &$variables) {
+  $variables['content']['remove_button']['#attributes']['class'][] = 'media-library-item__remove';
+}
+
+/**
+ * Implements hook_preprocess_media_library_item__small().
+ *
+ * This targets each pre-selected media item selected when adding new media in
+ * the modal media library dialog.
+ */
+function seven_preprocess_media_library_item__small(array &$variables) {
+  $variables['content']['select']['#attributes']['class'][] = 'media-library-item__click-to-select-checkbox';
+}
+
+/**
+ * @todo Remove this when https://www.drupal.org/project/drupal/issues/2999549
+ * lands.
+ *
+ * @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
+ */
+function seven_preprocess_fieldset__media_library_widget(array &$variables) {
+  if (isset($variables['prefix']['weight_toggle'])) {
+    $variables['prefix']['weight_toggle']['#attributes']['class'][] = 'media-library-widget__toggle-weight';
+  }
+  if (isset($variables['suffix']['open_button'])) {
+    $variables['suffix']['open_button']['#attributes']['class'][] = 'media-library-open-button';
+  }
+}
+
+/**
+ * Implements hook_views_pre_render().
+ */
+function seven_views_pre_render(ViewExecutable $view) {
+  $add_classes = function (&$option, array $classes_to_add) {
+    $classes = preg_split('/\s+/', $option);
+    $classes = array_filter($classes);
+    $classes = array_merge($classes, $classes_to_add);
+    $option = implode(' ', array_unique($classes));
+  };
+
+  if ($view->id() === 'media_library') {
+    if ($view->display_handler->options['defaults']['css_class']) {
+      $add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view']);
+    }
+    else {
+      $add_classes($view->display_handler->options['css_class'], ['media-library-view']);
+    }
+
+    if ($view->current_display === 'page') {
+      if (array_key_exists('media_bulk_form', $view->field)) {
+        $add_classes($view->field['media_bulk_form']->options['element_class'], ['media-library-item__click-to-select-checkbox']);
+      }
+      if (array_key_exists('rendered_entity', $view->field)) {
+        $add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
+      }
+      if (array_key_exists('edit_media', $view->field)) {
+        $add_classes($view->field['edit_media']->options['alter']['link_class'], ['media-library-item__edit']);
+      }
+      if (array_key_exists('delete_media', $view->field)) {
+        $add_classes($view->field['delete_media']->options['alter']['link_class'], ['media-library-item__remove']);
+      }
+    }
+    elseif (strpos($view->current_display, 'widget') === 0) {
+      if (array_key_exists('rendered_entity', $view->field)) {
+        $add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
+      }
+      if (array_key_exists('media_library_select_form', $view->field)) {
+        $add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['media-library-item__click-to-select-checkbox']);
+      }
+
+      if ($view->display_handler->options['defaults']['css_class']) {
+        $add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view--widget']);
+      }
+      else {
+        $add_classes($view->display_handler->options['css_class'], ['media-library-view--widget']);
+      }
+    }
+  }
+}
diff --git a/core/themes/seven/templates/media-library/details--media-library-add-form-selected-media.html.twig b/core/themes/seven/templates/media-library/details--media-library-add-form-selected-media.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..6d62c3fced7f6288381cb1b977173ad05bfdf28c
--- /dev/null
+++ b/core/themes/seven/templates/media-library/details--media-library-add-form-selected-media.html.twig
@@ -0,0 +1,20 @@
+{% extends "details.html.twig" %}
+{#
+/**
+ * @file
+ * Theme override for the "Additional selected media" area of the modal media
+ * library dialog.
+ *
+ * Available variables
+ * - attributes: A list of HTML attributes for the details element.
+ * - errors: (optional) Any errors for this details element, may not be set.
+ * - title: (optional) The title of the element, may not be set.
+ * - summary_attributes: A list of HTML attributes for the summary element.
+ * - description: (optional) The description of the element, may not be set.
+ * - children: (optional) The children of the element, may not be set.
+ * - value: (optional) The value of the element, may not be set.
+ *
+ * @see template_preprocess_details()
+ */
+#}
+{% set attributes = attributes.addClass('media-library-add-form__selected-media seven-details') %}
diff --git a/core/themes/seven/templates/media-library/fieldset--media-library-widget.html.twig b/core/themes/seven/templates/media-library/fieldset--media-library-widget.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..cd7044c9696d27e6fb0b17d666b79ddeb5b19a5b
--- /dev/null
+++ b/core/themes/seven/templates/media-library/fieldset--media-library-widget.html.twig
@@ -0,0 +1,64 @@
+{#
+/**
+ * @file
+ * Theme override for the media library widget.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the fieldset element.
+ * - errors: (optional) Any errors for this fieldset element, may not be set.
+ * - required: Boolean indicating whether the fieldeset element is required.
+ * - legend: The legend element containing the following properties:
+ *   - title: Title of the fieldset, intended for use as the text of the legend.
+ *   - attributes: HTML attributes to apply to the legend.
+ * - description: The description element containing the following properties:
+ *   - content: The description content of the fieldset.
+ *   - attributes: HTML attributes to apply to the description container.
+ * - children: The rendered child elements of the fieldset.
+ * - prefix: The content to add before the fieldset children.
+ * - suffix: The content to add after the fieldset children.
+ *
+ * @see seven_preprocess_fieldset__media_library_widget()
+ * @see template_preprocess_fieldset()
+ */
+#}
+{%
+  set classes = [
+    'js-form-item',
+    'form-item',
+    'js-form-wrapper',
+    'form-wrapper',
+    'media-library-widget',
+  ]
+%}
+<fieldset{{ attributes.addClass(classes) }}>
+  {%
+    set legend_span_classes = [
+      'fieldset-legend',
+      required ? 'js-form-required',
+      required ? 'form-required',
+    ]
+  %}
+  {#  Always wrap fieldset legends in a <span> for CSS positioning. #}
+  <legend{{ legend.attributes }}>
+    <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+  </legend>
+  <div class="fieldset-wrapper">
+    {% if errors %}
+      <div class="form-item--error-message">
+        <strong>{{ errors }}</strong>
+      </div>
+    {% endif %}
+    {% if prefix.empty_selection %}
+      <p class="media-library-widget-empty-text">{{ prefix.empty_selection }}</p>
+    {% elseif prefix.weight_toggle %}
+      {{ prefix.weight_toggle }}
+    {% endif %}
+    {{ children }}
+    {% if suffix %}
+      <span class="field-suffix">{{ suffix }}</span>
+    {% endif %}
+    {% if description.content %}
+      <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div>
+    {% endif %}
+  </div>
+</fieldset>
diff --git a/core/themes/seven/templates/media-library/item-list--media-library-add-form-media-list.html.twig b/core/themes/seven/templates/media-library/item-list--media-library-add-form-media-list.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..27cfe2bd26ae634980ce5bd5d4aed6784dfd956e
--- /dev/null
+++ b/core/themes/seven/templates/media-library/item-list--media-library-add-form-media-list.html.twig
@@ -0,0 +1,33 @@
+{#
+/**
+ * @file
+ * Theme override for a list of new, unsaved media items being added in the
+ * modal media library dialog.
+ *
+ * Available variables:
+ * - items: A list of items. Each item contains:
+ *   - attributes: HTML attributes to be applied to each list item.
+ *   - value: The content of the list element.
+ * - title: The title of the list.
+ * - list_type: The tag for list element ("ul" or "ol").
+ * - wrapper_attributes: HTML attributes to be applied to the list wrapper.
+ * - attributes: HTML attributes to be applied to the list.
+ * - empty: A message to display when there are no items. Allowed value is a
+ *   string or render array.
+ * - context: A list of contextual data associated with the list. May contain:
+ *   - list_style: The custom list style.
+ *
+ * @see seven_preprocess_item_list__media_library_add_form_media_list()
+ * @see template_preprocess_item_list()
+ */
+#}
+{% if items -%}
+  {%- if title is not empty -%}
+    <h3>{{ title }}</h3>
+  {%- endif -%}
+  <{{ list_type }}{{ attributes.addClass('media-library-add-form__added-media') }}>
+  {%- for item in items -%}
+    <li{{ item.attributes.addClass('media-library-add-form__media') }}>{{ item.value }}</li>
+  {%- endfor -%}
+  </{{ list_type }}>
+{%- endif %}
diff --git a/core/themes/seven/templates/media-library/views-view--media_library.html.twig b/core/themes/seven/templates/media-library/views-view--media_library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..b3fde507eb0cebd4a448b73e0b9e234bd9a309e2
--- /dev/null
+++ b/core/themes/seven/templates/media-library/views-view--media_library.html.twig
@@ -0,0 +1,95 @@
+{#
+/**
+ * @file
+ * Theme override for the media_library view template.
+ *
+ * Available variables:
+ * - attributes: Remaining HTML attributes for the element.
+ * - css_name: A css-safe version of the view name.
+ * - css_class: The user-specified classes names, if any.
+ * - header: The optional header.
+ * - footer: The optional footer.
+ * - rows: The results of the view query, if any.
+ * - empty: The content to display if there are no rows.
+ * - pager: The optional pager next/prev links to display.
+ * - exposed: Exposed widget form/info to display.
+ * - feed_icons: Optional feed icons to display.
+ * - more: An optional link to the next page of results.
+ * - title: Title of the view, only used when displaying in the admin preview.
+ * - title_prefix: Additional output populated by modules, intended to be
+ *   displayed in front of the view title.
+ * - title_suffix: Additional output populated by modules, intended to be
+ *   displayed after the view title.
+ * - attachment_before: An optional attachment view to be displayed before the
+ *   view content.
+ * - attachment_after: An optional attachment view to be displayed after the
+ *   view content.
+ * - dom_id: Unique id for every view being printed to give unique class for
+ *   Javascript.
+ *
+ * @see template_preprocess_views_view()
+ */
+#}
+{%
+  set classes = [
+    'view',
+    'view-' ~ id|clean_class,
+    'view-id-' ~ id,
+    'view-display-id-' ~ display_id,
+    dom_id ? 'js-view-dom-id-' ~ dom_id,
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
+  {{ title_prefix }}
+  {% if title %}
+    {{ title }}
+  {% endif %}
+  {{ title_suffix }}
+  {% if exposed %}
+    <div class="view-filters">
+      {{ exposed }}
+    </div>
+  {% endif %}
+  {% if header %}
+    <div class="view-header">
+      {{ header }}
+    </div>
+  {% endif %}
+  {% if attachment_before %}
+    <div class="attachment attachment-before">
+      {{ attachment_before }}
+    </div>
+  {% endif %}
+
+  {% if rows %}
+    <div class="view-content">
+      {{ rows }}
+    </div>
+  {% elseif empty %}
+    <div class="view-empty">
+      {{ empty }}
+    </div>
+  {% endif %}
+
+  {% if pager %}
+    {{ pager }}
+  {% endif %}
+  {% if attachment_after %}
+    <div class="attachment attachment-after">
+      {{ attachment_after }}
+    </div>
+  {% endif %}
+  {% if more %}
+    {{ more }}
+  {% endif %}
+  {% if footer %}
+    <div class="view-footer">
+      {{ footer }}
+    </div>
+  {% endif %}
+  {% if feed_icons %}
+    <div class="feed-icons">
+      {{ feed_icons }}
+    </div>
+  {% endif %}
+</div>
diff --git a/core/themes/seven/templates/media-library/views-view-unformatted--media-library.html.twig b/core/themes/seven/templates/media-library/views-view-unformatted--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..4bc33e0e869e5f9f17df02da1355434f06357964
--- /dev/null
+++ b/core/themes/seven/templates/media-library/views-view-unformatted--media-library.html.twig
@@ -0,0 +1,34 @@
+{#
+/**
+ * @file
+ * Theme override for the media_library display of unformatted rows.
+ *
+ * Available variables:
+ * - title: The title of this group of rows. May be empty.
+ * - rows: A list of the view's row items.
+ *   - attributes: The row's HTML attributes.
+ *   - content: The row's content.
+ * - view: The view object.
+ * - default_row_class: A flag indicating whether default classes should be
+ *   used on rows.
+ *
+ * @see template_preprocess_views_view_unformatted()
+ */
+#}
+{% if title %}
+  <h3>{{ title }}</h3>
+{% endif %}
+<div class="media-library-views-form__rows">
+  {% for row in rows %}
+    {%
+      set row_classes = [
+        default_row_class ? 'views-row',
+        'media-library-item',
+        'media-library-item--grid',
+      ]
+    %}
+    <div{{ row.attributes.addClass(row_classes) }}>
+      {{- row.content -}}
+    </div>
+  {% endfor %}
+</div>
diff --git a/core/themes/stable/templates/media-library/media--media-library.html.twig b/core/themes/stable/templates/media-library/media--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..6262e7d5062efd4fd0fad7a4676f2e4caa78acce
--- /dev/null
+++ b/core/themes/stable/templates/media-library/media--media-library.html.twig
@@ -0,0 +1,49 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a media entity in the media library.
+ *
+ * Available variables:
+ * - media: The entity with limited access to object properties and methods.
+ *   Only method names starting with "get", "has", or "is" and a few common
+ *   methods such as "id", "label", and "bundle" are available. For example:
+ *   - entity.getEntityTypeId() will return the entity type ID.
+ *   - entity.hasField('field_example') returns TRUE if the entity includes
+ *     field_example. (This does not indicate the presence of a value in this
+ *     field.)
+ *   Calling other methods, such as entity.delete(), will result in an exception.
+ *   See \Drupal\Core\Entity\EntityInterface for a full list of methods.
+ * - name: Name of the media.
+ * - content: Media content.
+ * - title_prefix: Additional output populated by modules, intended to be
+ *   displayed in front of the main title tag that appears in the template.
+ * - title_suffix: Additional output populated by modules, intended to be
+ *   displayed after the main title tag that appears in the template.
+ * - view_mode: View mode; for example, "teaser" or "full".
+ * - attributes: HTML attributes for the containing element.
+ * - title_attributes: Same as attributes, except applied to the main title
+ *   tag that appears in the template.
+ * - url: Direct URL of the media.
+ * - preview_attributes: HTML attributes for the preview wrapper.
+ * - metadata_attributes: HTML attributes for the expandable metadata area.
+ * - status: Whether or not the Media is published.
+ *
+ * @see template_preprocess_media()
+ * @see media_library_preprocess_media()
+ *
+ * @ingroup themeable
+ */
+#}
+<article{{ attributes }}>
+  {% if content %}
+    <div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
+      {{ content|without('name') }}
+    </div>
+    {% if not status %}
+      {{ "unpublished" | t }}
+    {% endif %}
+    <div{{ metadata_attributes }}>
+      {{ name }}
+    </div>
+  {% endif %}
+</article>
diff --git a/core/themes/stable/templates/media-library/media-library-item.html.twig b/core/themes/stable/templates/media-library/media-library-item.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..a765d04c11be9e7095c95289c71a8441dd2227a4
--- /dev/null
+++ b/core/themes/stable/templates/media-library/media-library-item.html.twig
@@ -0,0 +1,22 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a media library item.
+ *
+ * This is used when displaying selected media items, either in the field
+ * widget or in the "Additional selected media" area when adding new
+ * media items in the media library modal dialog.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - content: The content of the media library item, plus any additional
+ *   fields or elements surrounding it.
+ *
+ * @see template_preprocess_media_library_item()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {{ content }}
+</div>
diff --git a/core/themes/stable/templates/media-library/media-library-wrapper.html.twig b/core/themes/stable/templates/media-library/media-library-wrapper.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..344637cd62c27b3a5d8a5ebb2394a0d26eaaaf1f
--- /dev/null
+++ b/core/themes/stable/templates/media-library/media-library-wrapper.html.twig
@@ -0,0 +1,21 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a container used to wrap the media library's
+ * modal dialog interface.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - menu: The menu of availble media types to choose from.
+ * - content: The form to add new media items, followed by the grid or table of
+ *   existing media items to choose from.
+ *
+ * @see template_preprocess_media_library_wrapper()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {{ menu }}
+  {{ content }}
+</div>