diff --git a/core/misc/icons/787878/check.svg b/core/misc/icons/787878/check.svg
new file mode 100644
index 0000000..9894514
--- /dev/null
+++ b/core/misc/icons/787878/check.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px">
+<path fill="#787878" d="M6.464,13.676c-0.194,0.194-0.513,0.194-0.707,0l-4.96-4.955c-0.194-0.193-0.194-0.513,0-0.707l1.405-1.407 c0.194-0.195,0.512-0.195,0.707,0l2.849,2.848c0.194,0.193,0.513,0.193,0.707,0l6.629-6.626c0.195-0.194,0.514-0.194,0.707,0 l1.404,1.404c0.193,0.194,0.193,0.513,0,0.707L6.464,13.676z"/>
+</svg>
diff --git a/core/modules/media/images/icons/generic-file.png b/core/modules/media/images/icons/generic-file.png
new file mode 100644
index 0000000..a5bd743
--- /dev/null
+++ b/core/modules/media/images/icons/generic-file.png
@@ -0,0 +1,5 @@
+‰PNG
+
+   IHDR   ´   ´   
+ö    tEXtSoftware Adobe ImageReadyqÉe<   PLTEÌÌÍôõõ}^œf   çIDATxÚìØ1
+Aßÿ?ö)-ÃèÃvö<7ÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÿŠž×® g¾Õs-RgK‡ÔáÒuºtD/PçKÔ…ÒûêFéuu¥ô¶ºSzY]*½«n•^U×Joª‹èN¡O^.çÐsýc'Ñý\ü{½»  )yeïù    IEND®B`‚
\ No newline at end of file
diff --git a/core/modules/media_library/config/optional/core.entity_view_mode.media.media_library.yml b/core/modules/media_library/config/optional/core.entity_view_mode.media.media_library.yml
new file mode 100644
index 0000000..8c9ddd3
--- /dev/null
+++ b/core/modules/media_library/config/optional/core.entity_view_mode.media.media_library.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.media_library
+label: 'Media Library'
+targetEntityType: media
+cache: true
diff --git a/core/modules/media_library/config/optional/views.view.media_library.yml b/core/modules/media_library/config/optional/views.view.media_library.yml
new file mode 100644
index 0000000..1359a50
--- /dev/null
+++ b/core/modules/media_library/config/optional/views.view.media_library.yml
@@ -0,0 +1,702 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.media.media_library
+  module:
+    - media
+    - media_library
+    - system
+    - user
+id: media_library
+label: Media
+module: views
+description: ''
+tag: ''
+base_table: media_field_data
+base_field: mid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'view media'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: 'Apply Filters'
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: false
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 25
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: â€¹â€¹
+            next: â€ºâ€º
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: media-library-item
+          default_row_class: true
+      row:
+        type: fields
+        options:
+          default_field_elements: true
+          inline: {  }
+          separator: ''
+          hide_empty: false
+      fields:
+        media_bulk_form:
+          id: media_bulk_form
+          table: media
+          field: media_bulk_form
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          entity_type: media
+          plugin_id: bulk_form
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          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: rendered_entity
+      filters:
+        status:
+          id: status
+          table: media_field_data
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: '1'
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: 'Publishing status'
+            description: null
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: true
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: true
+          group_info:
+            label: Published
+            description: ''
+            identifier: status
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items:
+              1:
+                title: Published
+                operator: '='
+                value: '1'
+              2:
+                title: Unpublished
+                operator: '='
+                value: '0'
+          plugin_id: boolean
+          entity_type: media
+          entity_field: status
+        name:
+          id: name
+          table: media_field_data
+          field: name
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: name_op
+            label: Name
+            description: ''
+            use_operator: false
+            operator: name_op
+            identifier: name
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: media
+          entity_field: name
+          plugin_id: string
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: bundle_op
+            label: 'Media type'
+            description: ''
+            use_operator: false
+            operator: bundle_op
+            identifier: type
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: 'Media type'
+            description: null
+            identifier: bundle
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items:
+              1: {  }
+              2: {  }
+              3: {  }
+          entity_type: media
+          entity_field: bundle
+          plugin_id: bundle
+      sorts:
+        created:
+          id: created
+          table: media_field_data
+          field: created
+          relationship: none
+          group_type: group
+          admin_label: ''
+          order: DESC
+          exposed: true
+          expose:
+            label: 'Newest first'
+          granularity: second
+          entity_type: media
+          entity_field: created
+          plugin_id: date
+      title: Media
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments:
+        bundle:
+          id: bundle
+          table: media_field_data
+          field: bundle
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: ignore
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: fixed
+          default_argument_options:
+            argument: ''
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          glossary: false
+          limit: 0
+          case: none
+          path_case: none
+          transform_dash: false
+          break_phrase: false
+          entity_type: media
+          entity_field: bundle
+          plugin_id: string
+      display_extenders: {  }
+      use_ajax: true
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  bundle_page:
+    display_plugin: page
+    id: bundle_page
+    display_title: 'Bundle Page'
+    position: 2
+    display_options:
+      display_extenders: {  }
+      path: admin/content/media/%bundle
+      display_description: ''
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  embed:
+    display_plugin: embed
+    id: embed
+    display_title: Embed
+    position: 4
+    display_options:
+      display_extenders: {  }
+      fields:
+        media_select_media:
+          id: media_select_media
+          table: media
+          field: media_select_media
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: select_media
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          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: rendered_entity
+      defaults:
+        fields: false
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  page:
+    display_plugin: page
+    id: page
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: admin/content/media
+      menu:
+        type: tab
+        title: Media
+        description: 'Allows users to administer Media'
+        expanded: false
+        parent: system.admin_content
+        weight: 5
+        context: '0'
+        menu_name: admin
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
+  widget:
+    display_plugin: page
+    id: widget
+    display_title: Widget
+    position: 4
+    display_options:
+      display_extenders: {  }
+      fields:
+        media_select_media:
+          id: media_select_media
+          table: media
+          field: media_select_media
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-checkbox
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          entity_type: media
+          plugin_id: select_media
+        rendered_entity:
+          id: rendered_entity
+          table: media
+          field: rendered_entity
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: media-library-item-content
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          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: rendered_entity
+      defaults:
+        fields: false
+        header: false
+      display_description: ''
+      path: admin/media_field_widget
+      header:
+        area:
+          id: area
+          table: views
+          field: area
+          relationship: none
+          group_type: group
+          admin_label: ''
+          empty: false
+          tokenize: false
+          content:
+            value: '<a href="/media/add" class="use-ajax button button-action button--primary button--small" data-dialog-type="modal" >Add New Media</a>'
+            format: full_html
+          plugin_id: text
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - 'url.query_args:sort_by'
+        - user.permissions
+      tags:
+        - 'config:core.entity_view_display.media.type_one.default'
+        - 'config:core.entity_view_display.media.type_two.default'
diff --git a/core/modules/media_library/css/media_library.css b/core/modules/media_library/css/media_library.css
new file mode 100644
index 0000000..2909e55
--- /dev/null
+++ b/core/modules/media_library/css/media_library.css
@@ -0,0 +1,192 @@
+/**
+* @file media_library.view.css
+*/
+
+#views-form-media-library-page, *[id^='views-form-media-library-widget-'], .media-library-selection {
+  display: flex;
+  flex-wrap: wrap;
+  align-content: space-between;
+}
+
+#edit-header, .view-content #edit-actions {
+  display: none;
+  flex-basis: 100%;
+}
+
+#drupal-modal .view-header {
+  margin: 1em 0;
+}
+
+.media-library-item {
+  justify-content: center;
+  vertical-align: top;
+  position: relative;
+  border: 2px solid #ebebeb;
+  outline: 6px solid transparent;
+  margin: 14px 14px 0 0;
+  width: 180px;
+  height: 230px;
+  cursor: pointer;
+  background: #ffffff;
+  transition: border-color .2s, outline-color .2s;
+}
+
+.media-library-item .field {
+  display: none;
+}
+
+.media-library-item .field--name-thumbnail {
+  display: block;
+}
+
+.media-library-item .field--name-thumbnail img {
+  width: 100%;
+  height: auto;
+}
+
+.media-library-item.media-library-item-in-widget, .media-library-item.is-hover.media-library-item-in-widget {
+  cursor: move;
+}
+
+.media-library-item.checked {
+  outline-color: #c7eaff;
+}
+
+.media-library-item::after {
+  display: block;
+  width: 25px;
+  height: 25px;
+  content: "";
+  position: absolute;
+  top: 0;
+  right: 0;
+  background: url('../../../misc/icons/787878/check.svg') #ffffff center no-repeat;
+  background-size: 16px 16px;
+  border: 1px solid #ccc;
+  border-radius: 20px;
+  margin: 5px 5px 0 0;
+  opacity: 0;
+  transition: opacity .2s, background 1s;
+}
+
+.media-library-item.is-hover {
+  border-color: #40b6ff;
+  cursor: pointer;
+}
+
+.media-library-item-checkbox {
+   display: none;
+}
+
+.media-library-item:hover .media-library-item-attributes,
+.media-library-item.checked::after,
+.media-library-item:hover .media-library-item-remove {
+  opacity: 1;
+}
+
+.media-library-item-attributes {
+  width: 140px;
+  margin: 10px 30px 15px 10px;
+}
+
+.media-library-item-attributes > div {
+  width: 100%;
+}
+
+.media-library-item-toggle {
+  display: block;
+  width: 25px;
+  height: 25px;
+  content: "";
+  cursor: pointer;
+  position: absolute;
+  top: 184px;
+  right: 0;
+  background: url('../../../misc/icons/bebebe/chevron-disc-right.svg') transparent center no-repeat;
+  background-size: 20px;
+  margin: 5px 5px 0 0;
+  transition: all 0.25s ease;
+}
+
+.expanded .media-library-item-toggle {
+    transform: rotate(90deg);
+}
+
+.media-library-item.expanded {
+  height: auto;
+  min-height: 270px;
+}
+
+.media-library-item.expanded .media-library-item-created,
+.media-library-item.expanded .media-library-item-type {
+  font-size: 12px;
+  line-height: 1.25em;
+  display: inline-block;
+}
+
+.media-library-item .field--name-thumbnail {
+  background-color: #989898;
+  border-bottom: 2px solid #ebebeb;
+  height: 180px;
+  width: 180px;
+  overflow: hidden;
+}
+
+.media-library-item .field--name-thumbnail img {
+  height: auto;
+  min-height: 180px;
+  min-width: 180px;
+  width: auto;
+}
+
+.media-library-item-name {
+  font-size: 14px;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  padding-bottom: 0.25em;
+  white-space: nowrap;
+}
+
+.expanded .media-library-item-name {
+  white-space: normal;
+}
+
+.media-library-item-created,
+.media-library-item-type {
+  font-size: 12px;
+  line-height: 1.25em;
+  display: none;
+}
+
+.media-library-item .media-library-item-remove {
+  position: absolute;
+  z-index: 1;
+  top: 0;
+  right: 0;
+  width: 24px;
+  height: 24px;
+  margin: 5px;
+  padding: 0;
+  background: url('../../../misc/icons/787878/ex.svg') #ffffff center no-repeat;
+  background-size: 16px 16px;
+  border: 1px solid #ccc;
+  border-radius: 20px;
+  color: transparent;
+  text-shadow: none;
+  opacity: 0;
+  transition: opacity .2s;
+}
+
+.media-library-item .media-library-item-remove:hover,
+.media-library-item .media-library-item-remove:focus,
+.media-library-item .media-library-item-remove.button:disabled {
+  background: url('../../../misc/icons/787878/ex.svg') #ffffff center no-repeat;
+  background-size: 16px 16px;
+  color: transparent;
+  opacity: 1;
+  text-shadow: none;
+}
+
+.media-library-open-button {
+  margin-top: 10px;
+}
diff --git a/core/modules/media_library/js/media_library.view.js b/core/modules/media_library/js/media_library.view.js
new file mode 100644
index 0000000..2aba2b4
--- /dev/null
+++ b/core/modules/media_library/js/media_library.view.js
@@ -0,0 +1,159 @@
+/**
+ * @file media_library.view.js
+ */
+
+(function ($, Drupal) {
+
+  "use strict";
+
+  // Locally-scoped variable that stores the current View selection. We track
+  // this in a variable as users may click a media item and then re-load the
+  // View with AJAX (filters, pagers, sorts, etc.), which will lose what is
+  // currently selected in the bulk form. This could likely be back-ported to
+  // core.
+  var MediaLibrarySelectedEntities = {};
+
+  /**
+   * Allows users to click on hidden Views select checkboxes by clicking on the
+   * media item (which is also a Views row).
+   */
+  Drupal.behaviors.MediaLibraryCheckboxes = {
+    attach: function (context) {
+      $('.media-library-item .field--name-thumbnail', context).once('media-library-checkbox').on('click', function (event) {
+        // Links inside the rendered media item should not be click-able.
+        event.preventDefault();
+        // Click the hidden checkbox when the media item is clicked.
+        var $input = $(this).parents('.media-library-item').find('.media-library-item-checkbox input');
+        $input.prop('checked', !$input.prop('checked'));
+        if ($input.prop('checked')) {
+          $(this).parents('.media-library-item').addClass('checked');
+        }
+        else {
+          $(this).parents('.media-library-item').removeClass('checked');
+        }
+
+        if ($('.media-library-item.checked').length) {
+          $('#edit-header').show();
+          $('.view-content #edit-actions').show();
+        }
+        else {
+          $('#edit-header').hide();
+          $('.view-content #edit-actions').hide();
+        }
+
+        $input.trigger('change');
+      });
+    }
+  };
+
+  /**
+   * Allows users to toggle metadata on media items.
+   */
+  Drupal.behaviors.MediaLibraryMetadataToggle = {
+    attach: function (context) {
+      // Add the toggle when javascript is available
+      $('.media-library-item-attributes', context).after('<span class="media-library-item-toggle"></span>');
+      $('.media-library-item-toggle').on('click', function () {
+        if ($(this).parents('.media-library-item').hasClass('expanded')) {
+          $(this).parents('.media-library-item').removeClass('expanded');
+        }
+        else {
+          $(this).parents('.media-library-item').addClass('expanded');
+        }
+      });
+    }
+  };
+
+  /**
+   * Adds hover effect to media items.
+   */
+  Drupal.behaviors.MediaLibraryHover = {
+    attach: function (context) {
+      $('.media-library-item .field--name-thumbnail', context).once('media-library-item').on('mouseover', function (){
+        $(this).parents('.media-library-item').addClass('is-hover');
+      }).on('mouseout', function(){
+        $(this).parents('.media-library-item').removeClass('is-hover');
+      });
+    }
+  };
+
+  /**
+   * Persists bulk and select form selections across AJAX View re-renders.
+   */
+  Drupal.behaviors.MediaLibraryPersistentSelection = {
+    attach: function (context) {
+      // For each View on the page, store the dom-id as a data attribute so
+      // that we can easily access it later. This is normally only stored in a
+      // class.
+      $('[class*="js-view-dom-id-"]', context).once('set-dom-id-data-attribute').each(function () {
+        var element = $(this);
+        var classes = $(this).attr('class').split(/\s+/);
+        $(classes).each(function (i, class_name) {
+          var dom_id = class_name.match(new RegExp('^js-view-dom-id-(.*)$'));
+          if (dom_id) {
+            element.attr('data-views-dom-id', dom_id[1]);
+            if (!MediaLibrarySelectedEntities[dom_id[1]]) {
+              MediaLibrarySelectedEntities[dom_id[1]] = [];
+            }
+          }
+        });
+      });
+
+      // Check/uncheck checkboxes in the view based on the selected media.
+      $('.media-library-item-checkbox input', context).once('data-media-select').each(function (i, element) {
+        $(this).on('change', function () {
+          var checked = $(this).prop('checked');
+          var views_dom_id = $(this).closest('[data-views-dom-id]').data('views-dom-id');
+          var entity = $(this).val();
+          if (checked) {
+            MediaLibrarySelectedEntities[views_dom_id].push(entity);
+          }
+          else {
+            MediaLibrarySelectedEntities[views_dom_id] = $(MediaLibrarySelectedEntities[views_dom_id]).filter(function (i, value) {
+              return !(entity == value);
+            });
+          }
+        });
+      });
+    }
+  };
+
+  /**
+   * Attaches extra POST data when the Views bulk form is submitted, to ensure
+   * that selections that may be off-screen persist during the submit process.
+   */
+  Drupal.behaviors.MediaLibraryViewsSubmit = {
+    attach: function (context, settings) {
+      $('[data-views-dom-id]', context).once('media-library-checkbox-submit').each(function () {
+        var $view = $(this);
+        var views_dom_id = $(this).data('views-dom-id');
+        $.each(MediaLibrarySelectedEntities[views_dom_id], function(index, items) {
+          $view.find('input[value="' + items + '"]').prop('checked', true);
+          $view.find('input[value="' + items + '"]').closest('.media-library-item').addClass('checked');
+        });
+
+        // Propagate all selected entities to the form submission.
+        $view.find('[data-media-select-submit]').each(function () {
+          var button = $(this);
+          $.each(Drupal.ajax.instances, function () {
+            if (this && $(this.element).is(button)) {
+              if (settings && settings.views && settings.views.ajaxViews) {
+                var ajaxViews = settings.views.ajaxViews;
+                for (var i in ajaxViews) {
+                  var ajaxViewsInstance = Drupal.views.instances[i];
+                  if (ajaxViewsInstance.settings.view_dom_id == views_dom_id) {
+                    this.submit = $.extend(this.submit, ajaxViewsInstance.settings, {
+                      selected_entities: MediaLibrarySelectedEntities[views_dom_id],
+                      media_library_input: settings.media_library.media_library_inputs[views_dom_id]
+                    });
+                  }
+                }
+              }
+            }
+          });
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media_library/js/media_library.widget.js b/core/modules/media_library/js/media_library.widget.js
new file mode 100644
index 0000000..a8ab262
--- /dev/null
+++ b/core/modules/media_library/js/media_library.widget.js
@@ -0,0 +1,26 @@
+/**
+ * @file media_library.widget.js
+ */
+
+(function ($, Drupal) {
+
+  "use strict";
+
+  /**
+   * Provides custom JS behaviors related to the media library field widget.
+   */
+  Drupal.behaviors.MediaLibraryWidget = {
+    attach: function (context) {
+      $('.media-library-selection', context).sortable({
+        tolerance: 'pointer',
+        stop: function(event, ui) {
+          // Update all the hidden "weight" fields.
+          $(event.target).children().each(function (index) {
+            $(this).find('.media-library-item-weight').val(index);
+          });
+        }
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/media_library/media_library.info.yml b/core/modules/media_library/media_library.info.yml
new file mode 100644
index 0000000..4bb4583
--- /dev/null
+++ b/core/modules/media_library/media_library.info.yml
@@ -0,0 +1,10 @@
+name: 'Media library'
+type: module
+description: 'Provides a library for adding and re-using Media Items.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
+dependencies:
+  - drupal:media
+  - drupal:views
+  - drupal:user
diff --git a/core/modules/media_library/media_library.install b/core/modules/media_library/media_library.install
new file mode 100644
index 0000000..9200048
--- /dev/null
+++ b/core/modules/media_library/media_library.install
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the media_library module.
+ */
+
+use Drupal\views\Entity\View;
+
+/**
+ * Implements hook_install().
+ */
+function media_library_install() {
+  // Disable the /admin/content view provided by media, so that we can properly
+  // override it.
+  /** @var \Drupal\views\Entity\View $view */
+  $view = View::load('media');
+  $display = &$view->getDisplay('media_page_list');
+  $display['display_options']['enabled'] = FALSE;
+  $view->save();
+}
diff --git a/core/modules/media_library/media_library.libraries.yml b/core/modules/media_library/media_library.libraries.yml
new file mode 100644
index 0000000..44e62ce
--- /dev/null
+++ b/core/modules/media_library/media_library.libraries.yml
@@ -0,0 +1,28 @@
+style:
+  version: VERSION
+  css:
+    theme:
+      css/media_library.css: {}
+
+view:
+  version: VERSION
+  js:
+    js/media_library.view.js: {}
+  css:
+    theme:
+      css/media_library.css: {}
+  dependencies:
+    - core/drupal
+    - core/drupal.dialog.ajax
+    - core/jquery
+    - core/jquery.once
+
+widget:
+  version: VERSION
+  js:
+    js/media_library.widget.js: {}
+  dependencies:
+    - core/drupal
+    - core/jquery
+    - core/jquery.ui.sortable
+    - media_library/style
diff --git a/core/modules/media_library/media_library.links.action.yml b/core/modules/media_library/media_library.links.action.yml
new file mode 100644
index 0000000..6a37769
--- /dev/null
+++ b/core/modules/media_library/media_library.links.action.yml
@@ -0,0 +1,5 @@
+media_library.add:
+  route_name: entity.media.add_page
+  title: 'Add media'
+  appears_on:
+    - view.media_library.page
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
new file mode 100644
index 0000000..73e96c4
--- /dev/null
+++ b/core/modules/media_library/media_library.module
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains hook implementation for the media_library module.
+ */
+
+use Drupal\views\Plugin\views\cache\CachePluginBase;
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_theme().
+ */
+function media_library_theme() {
+  return [
+    'media__media_library' => [
+      'base hook' => 'media',
+    ],
+  ];
+}
+
+/**
+ * Implements hook_views_post_render().
+ */
+function media_library_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
+  if ($view->id() === 'media_library') {
+    $output['#attached']['library'][] = 'media_library/view';
+  }
+}
+
+/**
+ * Implements hook_preprocess_media().
+ */
+function media_library_preprocess_media(&$variables) {
+  if ($variables['view_mode'] === 'media_library') {
+    /** @var \Drupal\media\MediaInterface $media */
+    $media = $variables['media'];
+    /** @var \Drupal\user\UserInterface $user */
+    $user = $media->getRevisionUser();
+    $variables['author_info'] = [
+      '#markup' => t('Created by %author', ['%author' => $user->getDisplayName()]),
+    ];
+    $variables['created_ago'] = [
+      '#markup' => _media_library_pretty_time($media->getCreatedTime()),
+    ];
+  }
+}
+
+/**
+ * Displays a timestamp in a human-readable fashion.
+ *
+ * @todo This might be suitable for core as a Twig function.
+ *
+ * @param int $time
+ *   A timestamp.
+ *
+ * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+ *   Markup representing a formatted time.
+ */
+function _media_library_pretty_time($time) {
+  $time = (int) $time;
+  $too_old = strtotime('-1 month');
+  // Show formatted time differences for edits younger than a month.
+  if ($time > $too_old) {
+    $diff = \Drupal::service('date.formatter')->formatTimeDiffSince($time, ['granularity' => 1]);
+    $time_pretty = t('@diff ago', ['@diff' => $diff]);
+  }
+  else {
+    $date = date('m/d/Y - h:i A', $time);
+    $time_pretty = t('on @date', ['@date' => $date]);
+  }
+  return $time_pretty;
+}
diff --git a/core/modules/media_library/media_library.views.inc b/core/modules/media_library/media_library.views.inc
new file mode 100644
index 0000000..6a8b182
--- /dev/null
+++ b/core/modules/media_library/media_library.views.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Implements hook_views_data().
+ */
+function media_library_views_data() {
+  $data = [];
+  // Registers an library select field per entity.
+  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $entity_info) {
+    $data[$entity_info->getBaseTable()][$entity_type . '_select_media'] = array(
+      'title' => t('Select media'),
+      'help' => t('Provides a field for selecting media entities in our media library view'),
+      'real field' => $entity_info->getKey('id'),
+      'field' => array(
+        'id' => 'select_media',
+      ),
+    );
+  }
+  return $data;
+}
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
new file mode 100644
index 0000000..a97e224
--- /dev/null
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -0,0 +1,365 @@
+<?php
+
+namespace Drupal\media_library\Plugin\Field\FieldWidget;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SortArray;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Drupal\media\Entity\Media;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+
+/**
+ * Plugin implementation of the 'media_library_widget' widget.
+ *
+ * @todo We don't support the "auto_create", "auto_create_bundle", or
+ * "handler" field settings. Determine how to document or address this.
+ *
+ * @FieldWidget(
+ *   id = "media_library_widget",
+ *   label = @Translation("Media Library"),
+ *   description = @Translation("Allows you to re-use Media Entities."),
+ *   field_types = {
+ *     "entity_reference"
+ *   },
+ *   multiple_values = TRUE
+ * )
+ */
+class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a MediaLibraryWidget widget.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the widget is associated.
+   * @param array $settings
+   *   The widget settings.
+   * @param array $third_party_settings
+   *   Any third party settings.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   Entity type manager service.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['third_party_settings'],
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    return $field_definition->getSetting('target_type') === 'media';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
+    $field_name = $this->fieldDefinition->getName();
+    $parents = $form['#parents'];
+
+    // Load the items for form rebuilds from the field state as they might not be
+    // in $form_state['values'] because of validation limitations.
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+    if ($field_state && isset($field_state['items'])) {
+      usort($field_state['items'], [SortArray::class, 'sortByWeightElement']);
+      $items->setValue($field_state['items']);
+    }
+
+    $build = parent::form($items, $form, $form_state, $get_delta);
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
+    $referenced_entities = $items->referencedEntities();
+    $view_builder = $this->entityTypeManager->getViewBuilder($this->getFieldSetting('target_type'));
+    $field_name = $this->fieldDefinition->getName();
+    $wrapper_id = $field_name . '-media-library-wrapper';
+    $parents = $form['#parents'];
+
+    $element += [
+      '#type' => 'fieldset',
+      '#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
+      'selection' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'class' => 'media-library-selection',
+        ],
+      ],
+      '#attributes' => [
+        'id' => $wrapper_id,
+        'class' => ['media-library-wrapper'],
+      ],
+    ];
+
+    if (empty($referenced_entities)) {
+      $element['empty_selection'] = [
+        '#markup' => $this->t('<p>No media selected.</p>'
+      )];
+    }
+    else {
+      foreach ($referenced_entities as $delta => $media_item) {
+        $element['selection'][$delta] = [
+          '#type' => 'container',
+          '#attributes' => [
+            'class' => ['media-library-item', 'media-library-item-in-widget'],
+          ],
+          'preview' => [
+            '#type' => 'container',
+            'view' => $view_builder->view($media_item, 'media_library'),
+            '#attached' => [
+              'library' => ['media_library/widget'],
+            ],
+            'remove_button' => [
+              '#type' => 'submit',
+              '#media_library_remove_delta' =>$delta,
+              '#name' => $field_name . '-' .  $delta . '-media-library-remove-button',
+              '#value' => $this->t('Remove'),
+              '#attributes' => [
+                'class' => ['media-library-item-remove'],
+              ],
+              '#ajax' => [
+                'callback' => [static::class, 'updateWidget'],
+                'wrapper' => $wrapper_id,
+              ],
+              '#submit' => [[static::class, 'removeItem']],
+              '#limit_validation_errors' => [array_merge($form['#parents'], [$field_name])],
+            ],
+          ],
+          'target_id' => [
+            '#type' => 'hidden',
+            '#value' => $media_item->id(),
+          ],
+          // This hidden value is set by JS when items are re-sorted.
+          'weight' => [
+            '#type' => 'hidden',
+            '#default_value' => $delta,
+            '#attributes' => [
+              'class' => ['media-library-item-weight'],
+            ],
+          ],
+        ];
+      }
+    }
+
+    // @todo We use a use-ajax link here as it is the only way we've seen
+    // contextual filters work when using a view in a modal. Unfortunately
+    // this only works the first time you open the modal - opening it twice
+    // in one page load breaks Views AJAX.
+    $element['media_library_open_container'] = [
+      '#type' => 'container',
+      'media_library_open_button' => [
+        '#type' => 'link',
+        '#title' => $this->t('Add Media'),
+        '#name' => $field_name . '-media-library-open-button',
+        '#url' => Url::fromRoute('view.media_library.widget', [], [
+          'query' => ['media_library_input' => $field_name],
+        ]),
+        '#attributes' => [
+          'class' => ['button', 'use-ajax', 'media-library-open-button'],
+          'data-dialog-type' => 'modal',
+          'data-dialog-options' => json_encode([
+            'height' => '75%',
+            'width' => '75%',
+            'title' => $this->t('Media library'),
+          ]),
+        ],
+        '#limit_validation_errors' => [array_merge($parents, [$field_name])],
+      ],
+    ];
+
+    // Add a hidden textfield to the form, which is used by the select_media
+    // Views field to pass values from the modal to the widget. This is similar
+    // to how the EntityReferenceAutocompleteWidget stores values.
+    $element['media_library_selection'] = [
+      '#type' => 'textfield',
+      '#attributes' => [
+        'data-media-library-input-id' => $field_name,
+        'class' => ['visually-hidden'],
+      ],
+    ];
+
+    $element['media_library_update_widget'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update widget'),
+      '#name' => $field_name . '-media-library-update-button',
+      '#ajax' => [
+        'callback' => [static::class, 'updateWidget'],
+        'wrapper' => $wrapper_id,
+      ],
+      '#attributes' => [
+        'data-media-library-submit-id' => $field_name,
+        'class' => ['visually-hidden', 'media-library-update-button'],
+      ],
+      '#submit' => [[static::class, 'updateItems']],
+      '#limit_validation_errors' => [array_merge($parents, [$field_name])],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
+    return isset($element['target_id']) ? $element['target_id'] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    if (isset($values['selection'])) {
+      usort($values['selection'], [SortArray::class, 'sortByWeightElement']);
+      return $values['selection'];
+    }
+    else {
+      return [];
+    }
+  }
+
+  /**
+   * AJAX callback to update the widget when the selection changes.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   An array representing the updated widget.
+   */
+  public static function updateWidget($form, FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+    $length = isset($triggering_element['#media_library_remove_delta']) ? -4 : -1;
+    $parents = $triggering_element['#array_parents'];
+    $parents = array_slice($parents, 0, $length);
+    $element = NestedArray::getValue($form, $parents);
+    // Always clear the textfield selection to prevent duplicate additions.
+    $element['media_library_selection']['#value'] = '';
+    return $element;
+  }
+
+  /**
+   * Submit callback for remove buttons.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public static function removeItem($form, FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+
+    $parents = $triggering_element['#array_parents'];
+    $parents = array_slice($parents, 0, -4);
+    $element = NestedArray::getValue($form, $parents);
+    $field_name = $element['#field_name'];
+    $parents = $element['#field_parents'];
+    $delta = $triggering_element['#media_library_remove_delta'];
+
+    // Find and remove correct entity.
+    $path = array_merge($form['#parents'], [$field_name]);
+    $values = NestedArray::getValue($form_state->getValues(), $path);
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+    if (isset($values['selection'])) {
+      if (isset($values['selection'][$delta])) {
+        array_splice($values['selection'], $delta, 1);
+        $field_state['items'] = $values['selection'];
+        $field_state['items_count'] = count($field_state['items']);
+      }
+    }
+    $form_state->setValue($field_name, $values);
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+
+    // Rebuild form.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Updates the field state based on the form state and sets the form rebuild.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public static function updateItems($form, FormStateInterface $form_state) {
+    $button = $form_state->getTriggeringElement();
+
+    // Go one level up in the form, to the widgets container.
+    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
+    $field_name = $element['#field_name'];
+    $parents = $element['#field_parents'];
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+
+    // Get the new media ids passed to our hidden button.
+    $values = $form_state->getValues();
+    $path = array_merge($form['#parents'], [$field_name]);
+    $value = NestedArray::getValue($values, $path);
+    $selection = isset($value['selection']) ? $value['selection'] : [];
+
+    $field_state['items'] = $selection;
+
+    if (!empty($value['media_library_selection'])) {
+      $ids = explode(',', $value['media_library_selection']);
+      /** @var \Drupal\media\MediaInterface[] $media */
+      $media = Media::loadMultiple($ids);
+
+      // Append the provided Media to the existing selection.
+      $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+      foreach ($media as $media_item) {
+        if ($cardinality_unlimited || (count($field_state['items']) < $element['#cardinality'])) {
+          if ($media && $media_item->access('view')) {
+            $field_state['items'][] = [
+              'target_id' => $media_item->id(),
+            ];
+          }
+        }
+      }
+    }
+
+    $field_state['items_count'] = count($field_state['items']);
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+
+    $form_state->setRebuild();
+  }
+
+}
diff --git a/core/modules/media_library/src/Plugin/views/field/SelectMedia.php b/core/modules/media_library/src/Plugin/views/field/SelectMedia.php
new file mode 100644
index 0000000..4b18a39
--- /dev/null
+++ b/core/modules/media_library/src/Plugin/views/field/SelectMedia.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\media_library\Plugin\views\field;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
+use Drupal\views\Plugin\views\field\SelectFormBase;
+
+/**
+ * Provides a field for selecting media entities in our media library view.
+ *
+ * @ViewsField("select_media")
+ */
+class SelectMedia extends SelectFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewsForm(&$form, FormStateInterface $form_state) {
+    parent::viewsForm($form, $form_state);
+    if (!empty($form[$this->options['id']])) {
+      foreach (Element::children($form[$this->options['id']]) as $index) {
+        $item = &$form[$this->options['id']][$index];
+        $item['#attributes']['data-media-select'] = TRUE;
+      }
+    }
+    if (isset($form['actions']['submit'])) {
+      $form['actions']['submit']['#attributes']['data-media-select-submit'] = TRUE;
+      $form['actions']['submit']['#ajax']['url'] = Url::fromRoute('<current>');
+      $form['actions']['submit']['#ajax']['callback'] = function ($form, FormStateInterface $form_state) {
+        $response = new AjaxResponse();
+        $response->addCommand(new CloseModalDialogCommand());
+        $form_state->setResponse($response);
+      };
+    }
+    if ($media_library_input = \Drupal::request()->get('media_library_input')) {
+      $form['#attached']['drupalSettings']['media_library']['media_library_inputs'][$this->view->dom_id] = $media_library_input;
+    }
+  }
+
+  /**
+   * Submit handler for the bulk form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the user tried to access an action without access to it.
+   */
+  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
+    if ($form_state->get('step') == 'views_form_views_form') {
+      // Prefer the js provided selection, because it contains selected entities
+      // that aren't currently on screen and tracks the order of the selection.
+      if (\Drupal::request()->request->has('selected_entities')) {
+        $selected = \Drupal::request()->request->get('selected_entities');
+      }
+      else {
+        // Filter only selected checkboxes.
+        $selected = array_filter($form_state->getValue($this->options['id']));
+      }
+
+      $entities = [];
+
+      foreach ($selected as $bulk_form_key) {
+        $entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
+        $entities[$bulk_form_key] = $entity;
+      }
+
+      $ids = [];
+      foreach ($entities as $entity) {
+        $ids[] = $entity->id();
+      }
+
+      $response = new AjaxResponse();
+      $library_input = \Drupal::request()->get('media_library_input');
+      $input_selector = '[data-media-library-input-id="' . $library_input  . '"]';
+      $submit_selector = '[data-media-library-submit-id="' . $library_input  . '"]';
+      $response->addCommand(new InvokeCommand($input_selector, 'val', [implode(',', $ids)]));
+      $response->addCommand(new InvokeCommand($submit_selector, 'trigger', ['mousedown']));
+      $response->addCommand(new CloseModalDialogCommand());
+      $form_state->setResponse($response);
+    }
+  }
+
+}
diff --git a/core/modules/media_library/templates/media--media-library.html.twig b/core/modules/media_library/templates/media--media-library.html.twig
new file mode 100644
index 0000000..84ac7a9
--- /dev/null
+++ b/core/modules/media_library/templates/media--media-library.html.twig
@@ -0,0 +1,50 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a media entity in the media library.
+ *
+ * The major difference between this template and the default template is that
+ * "name" is omitted, as "name" is output as a field in the Media library view.
+ * Other themes are welcome to override this template, which will not break the
+ * functionality of 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.
+ * - author_info: This is a Media Library specific variable that contains the
+ *   display name of the author for this media.
+ * - created_ago: This is a Media Library specific variable that contains a
+ *   formatted "time ago" version of the created date.
+ *
+ * @see template_preprocess_media()
+ *
+ * @ingroup themeable
+ */
+#}
+<article{{ attributes }}>
+  {% if content %}
+    {{ content|without('name', 'created', 'uid') }}
+    <div class="media-library-item-attributes">
+      <div class="media-library-item-name">{{ name }}</div>
+      <div class="media-library-item-created">{{ created_ago }}</div>
+      <div class="media-library-item-type">{{ media.bundle()|title }}</div>
+    </div>
+  {% endif %}
+</article>
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml
new file mode 100644
index 0000000..212daf8
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_one.field_media_test
+    - media.type.type_one
+id: media.type_one.default
+targetEntityType: media
+bundle: type_one
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test:
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+    type: string_textfield
+    weight: 11
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml
new file mode 100644
index 0000000..fabd13b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml
@@ -0,0 +1,43 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_two.field_media_test_1
+    - media.type.type_two
+id: media.type_two.default
+targetEntityType: media
+bundle: type_two
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test_1:
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+    type: string_textfield
+    weight: 11
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml
new file mode 100644
index 0000000..775319d
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml
@@ -0,0 +1,71 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.media_library_test.field_media
+    - field.field.node.media_library_test.field_multivalue_media
+    - node.type.media_library_test
+  module:
+    - media_library
+    - path
+id: node.media_library_test.default
+targetEntityType: node
+bundle: media_library_test
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media:
+    weight: 31
+    settings: {  }
+    third_party_settings: {  }
+    type: media_library_widget
+    region: content
+  field_multivalue_media:
+    weight: 32
+    settings: {  }
+    third_party_settings: {  }
+    type: media_library_widget
+    region: content
+  path:
+    type: path
+    weight: 30
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  promote:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 15
+    region: content
+    third_party_settings: {  }
+  sticky:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 16
+    region: content
+    third_party_settings: {  }
+  title:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml
new file mode 100644
index 0000000..8f5f22a
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_one.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_one.field_media_test
+    - image.style.thumbnail
+    - media.type.type_one
+  module:
+    - image
+    - user
+id: media.type_one.default
+targetEntityType: media
+bundle: type_one
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 0
+    region: content
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_media_test:
+    label: above
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+    type: string
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml
new file mode 100644
index 0000000..a884fee
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_two.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_two.field_media_test_1
+    - image.style.thumbnail
+    - media.type.type_two
+  module:
+    - image
+    - user
+id: media.type_two.default
+targetEntityType: media
+bundle: type_two
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 0
+    region: content
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_media_test_1:
+    label: above
+    settings:
+      link_to_entity: false
+    third_party_settings: {  }
+    type: string
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml
new file mode 100644
index 0000000..3650d3b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml
@@ -0,0 +1,34 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.media_library_test.field_media
+    - field.field.node.media_library_test.field_multivalue_media
+    - node.type.media_library_test
+  module:
+    - user
+id: node.media_library_test.default
+targetEntityType: node
+bundle: media_library_test
+mode: default
+content:
+  field_media:
+    weight: 101
+    label: above
+    settings:
+      link: true
+    third_party_settings: {  }
+    type: entity_reference_label
+    region: content
+  field_multivalue_media:
+    weight: 102
+    label: above
+    settings:
+      link: true
+    third_party_settings: {  }
+    type: entity_reference_label
+    region: content
+  links:
+    weight: 100
+    region: content
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml
new file mode 100644
index 0000000..f19207c
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_one.field_media_test.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test
+    - media.type.type_one
+id: media.type_one.field_media_test
+field_name: field_media_test
+entity_type: media
+bundle: type_one
+label: field_media_test
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml
new file mode 100644
index 0000000..5b093ca
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_two.field_media_test_1.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test_1
+    - media.type.type_two
+id: media.type_two.field_media_test_1
+field_name: field_media_test_1
+entity_type: media
+bundle: type_two
+label: field_media_test_1
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: string
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml
new file mode 100644
index 0000000..e5773ab
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_media
+    - media.type.type_one
+    - media.type.type_two
+    - node.type.media_library_test
+id: node.media_library_test.field_media
+field_name: field_media
+entity_type: node
+bundle: media_library_test
+label: Media
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  handler: 'default:media'
+  handler_settings:
+    target_bundles:
+      type_one: type_one
+      type_two: type_two
+    sort:
+      field: _none
+    auto_create: false
+    auto_create_bundle: type_one
+field_type: entity_reference
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml
new file mode 100644
index 0000000..f92f30f
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_multivalue_media
+    - media.type.type_one
+    - media.type.type_two
+    - node.type.media_library_test
+id: node.media_library_test.field_multivalue_media
+field_name: field_multivalue_media
+entity_type: node
+bundle: media_library_test
+label: 'Multivalue Media'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  handler: 'default:media'
+  handler_settings:
+    target_bundles:
+      type_one: type_one
+      type_two: type_two
+    sort:
+      field: _none
+    auto_create: false
+    auto_create_bundle: type_one
+field_type: entity_reference
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml
new file mode 100644
index 0000000..40a7791
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.field_media_test
+field_name: field_media_test
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml
new file mode 100644
index 0000000..73b1105
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_1.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.field_media_test_1
+field_name: field_media_test_1
+entity_type: media
+type: string
+settings:
+  max_length: 255
+  is_ascii: false
+  case_sensitive: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml
new file mode 100644
index 0000000..bbec21d
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - node
+id: node.field_media
+field_name: field_media
+entity_type: node
+type: entity_reference
+settings:
+  target_type: media
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml
new file mode 100644
index 0000000..9937919
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - node
+id: node.field_multivalue_media
+field_name: field_multivalue_media
+entity_type: node
+type: entity_reference
+settings:
+  target_type: media
+module: core
+locked: false
+cardinality: 5
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml
new file mode 100644
index 0000000..1f72b8b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_one.yml
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - media_test_source
+id: type_one
+label: 'Type One'
+description: ''
+source: test
+queue_thumbnail_downloads: false
+new_revision: false
+source_configuration:
+  source_field: field_media_test
+  test_config_value: 'This is default value.'
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml
new file mode 100644
index 0000000..13b549c
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_two.yml
@@ -0,0 +1,16 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+    - media_test_source
+id: type_two
+label: 'Type Two'
+description: ''
+source: test
+queue_thumbnail_downloads: false
+new_revision: false
+source_configuration:
+  source_field: field_media_test_1
+  test_config_value: 'This is default value.'
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml
new file mode 100644
index 0000000..2c3ba17
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - menu_ui
+third_party_settings:
+  menu_ui:
+    available_menus:
+      - main
+    parent: 'main:'
+name: 'Media Library Test'
+type: media_library_test
+description: ''
+help: ''
+new_revision: true
+preview_mode: 1
+display_submitted: true
diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
new file mode 100644
index 0000000..77629a6
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
@@ -0,0 +1,11 @@
+name: 'Media library test'
+type: module
+description: 'Test module for Media Library.'
+package: Testing
+core: 8.x
+dependencies:
+  - drupal:media_library
+  - drupal:media_test_source
+  - drupal:menu_ui
+  - drupal:node
+  - drupal:path
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
new file mode 100644
index 0000000..72ca4a7
--- /dev/null
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\Tests\media_library\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\media\Entity\Media;
+
+/**
+ * Contains Media Library integration tests.
+ *
+ * @group media_library
+ */
+class MediaLibraryTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['block', 'media_library_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create a few example media items for use in selection.
+    $media = [
+      'type_one' => [
+        'media_1',
+        'media_2',
+      ],
+      'type_two' => [
+        'media_3',
+        'media_4',
+      ],
+    ];
+
+    foreach ($media as $type => $names) {
+      foreach ($names as $name) {
+        Media::create(['name' => $name, 'bundle' => $type])->save();
+      }
+    }
+
+    // Create a user who can use the Media Library.
+    $user = $this->drupalCreateUser([
+      'access content',
+      'access content overview',
+      'access administration pages',
+      'administer media',
+      'create media_library_test content',
+      'edit own media_library_test content',
+      'delete any media',
+      'delete own media_library_test content',
+      'view media',
+    ]);
+    $this->drupalLogin($user);
+    $this->drupalPlaceBlock('local_tasks_block');
+  }
+
+  /**
+   * Tests that the Media Library's administration page works as expected.
+   */
+  public function testAdministrationPage() {
+    // Visit the administration page.
+    $this->drupalGet('admin/content/media');
+
+    // Verify that media from two separate types is present.
+    $this->assertSession()->pageTextContains('media_1');
+    $this->assertSession()->pageTextContains('media_3');
+
+    // Test the tabs that allow users to filter by type.
+    $this->clickLink('Type One');
+    $this->assertSession()->pageTextContains('media_2');
+    $this->assertSession()->pageTextNotContains('media_4');
+    $this->clickLink('Type Two');
+    $this->assertSession()->pageTextNotContains('media_2');
+    $this->assertSession()->pageTextContains('media_4');
+
+    // Test that selecting elements as a part of bulk operations works.
+    $this->clickLink('All categories');
+    $this->getSession()->executeScript('jQuery(".media-library-item").click()');
+    $this->submitForm([], 'Apply to selected items', 'views-form-media-library-page');
+    $this->assertSession()->pageTextContains('media_1');
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php
index a253fc3..8f6df30 100644
--- a/core/modules/views/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/views/src/Plugin/views/field/BulkForm.php
@@ -2,15 +2,12 @@
 
 namespace Drupal\views\Plugin\views\field;
 
-use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\Routing\RedirectDestinationTrait;
 use Drupal\Core\TypedData\TranslatableInterface;
-use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\style\Table;
 use Drupal\views\ResultRow;
@@ -22,18 +19,7 @@
  *
  * @ViewsField("bulk_form")
  */
-class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
-
-  use RedirectDestinationTrait;
-  use UncacheableFieldHandlerTrait;
-  use EntityTranslationRenderTrait;
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
-   */
-  protected $entityManager;
+class BulkForm extends SelectFormBase {
 
   /**
    * The action storage.
@@ -50,13 +36,6 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
   protected $actions = [];
 
   /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
    * Constructs a new BulkForm object.
    *
    * @param array $configuration
@@ -71,11 +50,8 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
    *   The language manager.
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->entityManager = $entity_manager;
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $language_manager);
     $this->actionStorage = $entity_manager->getStorage('action');
-    $this->languageManager = $language_manager;
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/SelectFormBase.php
similarity index 55%
copy from core/modules/views/src/Plugin/views/field/BulkForm.php
copy to core/modules/views/src/Plugin/views/field/SelectFormBase.php
index a253fc3..abf66e9 100644
--- a/core/modules/views/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/views/src/Plugin/views/field/SelectFormBase.php
@@ -11,18 +11,14 @@
 use Drupal\Core\Routing\RedirectDestinationTrait;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
-use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\style\Table;
 use Drupal\views\ResultRow;
-use Drupal\views\ViewExecutable;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Defines a actions-based bulk operation form element.
- *
- * @ViewsField("bulk_form")
+ * Defines a base class select forms.
  */
-class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
+abstract class SelectFormBase extends FieldPluginBase implements CacheableDependencyInterface {
 
   use RedirectDestinationTrait;
   use UncacheableFieldHandlerTrait;
@@ -36,20 +32,6 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
   protected $entityManager;
 
   /**
-   * The action storage.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $actionStorage;
-
-  /**
-   * An array of actions that can be executed.
-   *
-   * @var \Drupal\system\ActionConfigEntityInterface[]
-   */
-  protected $actions = [];
-
-  /**
    * The language manager.
    *
    * @var \Drupal\Core\Language\LanguageManagerInterface
@@ -74,7 +56,6 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->entityManager = $entity_manager;
-    $this->actionStorage = $entity_manager->getStorage('action');
     $this->languageManager = $language_manager;
   }
 
@@ -94,19 +75,6 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
-    parent::init($view, $display, $options);
-
-    $entity_type = $this->getEntityType();
-    // Filter the actions to only include those for this entity type.
-    $this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
-      return $action->getType() == $entity_type;
-    });
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getCacheMaxAge() {
     // @todo Consider making the bulk operation form cacheable. See
     //   https://www.drupal.org/node/2503009.
@@ -158,61 +126,6 @@ protected function getView() {
   /**
    * {@inheritdoc}
    */
-  protected function defineOptions() {
-    $options = parent::defineOptions();
-    $options['action_title'] = ['default' => $this->t('Action')];
-    $options['include_exclude'] = [
-      'default' => 'exclude',
-    ];
-    $options['selected_actions'] = [
-      'default' => [],
-    ];
-    return $options;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
-    $form['action_title'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Action title'),
-      '#default_value' => $this->options['action_title'],
-      '#description' => $this->t('The title shown above the actions dropdown.'),
-    ];
-
-    $form['include_exclude'] = [
-      '#type' => 'radios',
-      '#title' => $this->t('Available actions'),
-      '#options' => [
-        'exclude' => $this->t('All actions, except selected'),
-        'include' => $this->t('Only selected actions'),
-      ],
-      '#default_value' => $this->options['include_exclude'],
-    ];
-    $form['selected_actions'] = [
-      '#type' => 'checkboxes',
-      '#title' => $this->t('Selected actions'),
-      '#options' => $this->getBulkOptions(FALSE),
-      '#default_value' => $this->options['selected_actions'],
-    ];
-
-    parent::buildOptionsForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateOptionsForm(&$form, FormStateInterface $form_state) {
-    parent::validateOptionsForm($form, $form_state);
-
-    $selected_actions = $form_state->getValue(['options', 'selected_actions']);
-    $form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function preRender(&$values) {
     parent::preRender($values);
 
@@ -261,7 +174,7 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
           '#type' => 'checkbox',
           // We are not able to determine a main "title" for each row, so we can
           // only output a generic label.
-          '#title' => $this->t('Update this item'),
+          '#title' => $this->t('Select this item'),
           '#title_display' => 'invisible',
           '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
           '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
@@ -269,27 +182,7 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
       }
 
       // Replace the form submit button label.
-      $form['actions']['submit']['#value'] = $this->t('Apply to selected items');
-
-      // Ensure a consistent container for filters/operations in the view header.
-      $form['header'] = [
-        '#type' => 'container',
-        '#weight' => -100,
-      ];
-
-      // Build the bulk operations action widget for the header.
-      // Allow themes to apply .container-inline on this separate container.
-      $form['header'][$this->options['id']] = [
-        '#type' => 'container',
-      ];
-      $form['header'][$this->options['id']]['action'] = [
-        '#type' => 'select',
-        '#title' => $this->options['action_title'],
-        '#options' => $this->getBulkOptions(),
-      ];
-
-      // Duplicate the form actions into the action container in the header.
-      $form['header'][$this->options['id']]['actions'] = $form['actions'];
+      $form['actions']['submit']['#value'] = $this->t('Selected items');
     }
     else {
       // Remove the default actions build array.
@@ -298,99 +191,6 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
   }
 
   /**
-   * Returns the available operations for this form.
-   *
-   * @param bool $filtered
-   *   (optional) Whether to filter actions to selected actions.
-   * @return array
-   *   An associative array of operations, suitable for a select element.
-   */
-  protected function getBulkOptions($filtered = TRUE) {
-    $options = [];
-    // Filter the action list.
-    foreach ($this->actions as $id => $action) {
-      if ($filtered) {
-        $in_selected = in_array($id, $this->options['selected_actions']);
-        // If the field is configured to include only the selected actions,
-        // skip actions that were not selected.
-        if (($this->options['include_exclude'] == 'include') && !$in_selected) {
-          continue;
-        }
-        // Otherwise, if the field is configured to exclude the selected
-        // actions, skip actions that were selected.
-        elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
-          continue;
-        }
-      }
-
-      $options[$id] = $action->label();
-    }
-
-    return $options;
-  }
-
-  /**
-   * Submit handler for the bulk form.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
-   *   Thrown when the user tried to access an action without access to it.
-   */
-  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
-    if ($form_state->get('step') == 'views_form_views_form') {
-      // Filter only selected checkboxes. Use the actual user input rather than
-      // the raw form values array, since the site data may change before the
-      // bulk form is submitted, which can lead to data loss.
-      $user_input = $form_state->getUserInput();
-      $selected = array_filter($user_input[$this->options['id']]);
-      $entities = [];
-      $action = $this->actions[$form_state->getValue('action')];
-      $count = 0;
-
-      foreach ($selected as $bulk_form_key) {
-        $entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
-
-        // Skip execution if the user did not have access.
-        if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
-          $this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
-            '%action' => $action->label(),
-            '@entity_type_label' => $entity->getEntityType()->getLabel(),
-            '%entity_label' => $entity->label()
-          ]), 'error');
-          continue;
-        }
-
-        $count++;
-
-        $entities[$bulk_form_key] = $entity;
-      }
-
-      $action->execute($entities);
-
-      $operation_definition = $action->getPluginDefinition();
-      if (!empty($operation_definition['confirm_form_route_name'])) {
-        $options = [
-          'query' => $this->getDestinationArray(),
-        ];
-        $form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
-      }
-      else {
-        // Don't display the message unless there are some elements affected and
-        // there is no confirmation form.
-        if ($count) {
-          drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
-            '%action' => $action->label(),
-          ]));
-        }
-      }
-    }
-  }
-
-  /**
    * Returns the message to be displayed when there are no selected items.
    *
    * @return string
