diff --git a/core/modules/system/css/system.admin.css b/core/modules/system/css/system.admin.css
index a6b9e568e9..756aa1c236 100644
--- a/core/modules/system/css/system.admin.css
+++ b/core/modules/system/css/system.admin.css
@@ -138,6 +138,12 @@ small .admin-link:after {
   padding-left: 12px;
   padding-right: 0;
 }
+.system-modules-installed td {
+  padding-left: inherit; /* LTR */
+}
+[dir="rtl"] .system-modules-installed td {
+  padding-right: inherit;
+}
 
 @media screen and (max-width: 40em) {
   .system-modules td.name {
diff --git a/core/modules/system/src/Controller/ModulesListInstalledController.php b/core/modules/system/src/Controller/ModulesListInstalledController.php
new file mode 100644
index 0000000000..6dcbaec683
--- /dev/null
+++ b/core/modules/system/src/Controller/ModulesListInstalledController.php
@@ -0,0 +1,275 @@
+<?php
+
+namespace Drupal\system\Controller;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
+use Drupal\user\PermissionHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides module installed list.
+ *
+ * The list of modules gets populated by module.info.yml files, which contain
+ * each module's name, description, and information about which modules it
+ * requires. See \Drupal\Core\Extension\InfoParser for info on module.info.yml
+ * descriptors.
+ *
+ * @internal
+ */
+class ModulesListInstalledController extends ControllerBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The permission handler.
+   *
+   * @var \Drupal\user\PermissionHandlerInterface
+   */
+  protected $permissionHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('module_handler'),
+      $container->get('access_manager'),
+      $container->get('current_user'),
+      $container->get('user.permissions')
+    );
+  }
+
+  /**
+   * Constructs a ModulesListForm object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
+   *   Access manager.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param \Drupal\user\PermissionHandlerInterface $permission_handler
+   *   The permission handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, AccessManagerInterface $access_manager, AccountInterface $current_user, PermissionHandlerInterface $permission_handler) {
+    $this->moduleHandler = $module_handler;
+    $this->accessManager = $access_manager;
+    $this->currentUser = $current_user;
+    $this->permissionHandler = $permission_handler;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listModules() {
+    $list = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => [
+          'system-modules',
+          'system-modules-installed',
+        ],
+      ],
+    ];
+
+    $list['filters'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['table-filter', 'js-show'],
+      ],
+    ];
+
+    $list['filters']['text'] = [
+      '#type' => 'search',
+      '#title' => $this->t('Filter modules'),
+      '#title_display' => 'invisible',
+      '#size' => 30,
+      '#placeholder' => $this->t('Filter by name or description'),
+      '#description' => $this->t('Enter a part of the module name or description'),
+      '#description_display' => 'after',
+      '#attributes' => [
+        'class' => ['table-filter-text'],
+        'data-table' => '.system-modules-installed',
+        'autocomplete' => 'off',
+      ],
+    ];
+
+    // Sort all modules by their names.
+    $modules = system_rebuild_module_data();
+    // Only list installed modules.
+    $modules_to_list = array_filter($modules, function (Extension $module) {
+      return $module->status == TRUE;
+    });
+    uasort($modules_to_list, 'system_sort_modules_by_info_name');
+
+    // Iterate over each of the modules.
+    foreach ($modules_to_list as $filename => $module) {
+      if (empty($module->info['hidden'])) {
+        $package = $module->info['package'];
+        $list['modules'][$package][$filename] = $this->buildRow($modules, $module);
+      }
+    }
+
+    // Add a wrapper around every package.
+    foreach (Element::children($list['modules']) as $package) {
+      $list['modules'][$package] += [
+        '#type' => 'details',
+        '#title' => $this->t($package),
+        '#open' => TRUE,
+        '#theme' => 'system_modules_installed',
+        '#attributes' => ['class' => ['package-listing']],
+        // Ensure that the "Core" package comes first.
+        '#weight' => $package == 'Core' ? -10 : NULL,
+      ];
+    }
+
+    // If testing modules are shown, collapse the corresponding package by
+    // default.
+    if (isset($list['modules']['Testing'])) {
+      $list['modules']['Testing']['#open'] = FALSE;
+    }
+
+    // Lastly, sort all packages by title.
+    uasort($list['modules'], ['\Drupal\Component\Utility\SortArray', 'sortByTitleProperty']);
+
+    $list['#attached']['library'][] = 'core/drupal.tableresponsive';
+    $list['#attached']['library'][] = 'system/drupal.system.modules';
+    return $list;
+  }
+
+  /**
+   * Builds a table row for the system modules page.
+   *
+   * @param array $modules
+   *   The list existing modules.
+   * @param \Drupal\Core\Extension\Extension $module
+   *   The module for which to build the form row.
+   *
+   * @return array
+   *   The form row for the given module.
+   */
+  protected function buildRow(array $modules, Extension $module) {
+    // Set the basic properties.
+    $row['#required'] = [];
+    $row['#requires'] = [];
+    $row['#required_by'] = [];
+
+    $row['name']['#markup'] = $module->info['name'];
+    $row['description']['#markup'] = $this->t($module->info['description']);
+    $row['version']['#markup'] = $module->info['version'];
+
+    // Generate link for module's help page. Assume that if a hook_help()
+    // implementation exists then the module provides an overview page, rather
+    // than checking to see if the page exists, which is costly.
+    if ($this->moduleHandler->moduleExists('help') && $module->status && in_array($module->getName(), $this->moduleHandler->getImplementations('help'))) {
+      $row['links']['help'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Help'),
+        '#url' => Url::fromRoute('help.page', ['name' => $module->getName()]),
+        '#options' => ['attributes' => ['class' => ['module-link', 'module-link-help'], 'title' => $this->t('Help')]],
+      ];
+    }
+
+    // Generate link for module's permission, if the user has access to it.
+    if ($module->status && $this->currentUser->hasPermission('administer permissions') && $this->permissionHandler->moduleProvidesPermissions($module->getName())) {
+      $row['links']['permissions'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Permissions'),
+        '#url' => Url::fromRoute('user.admin_permissions'),
+        '#options' => ['fragment' => 'module-' . $module->getName(), 'attributes' => ['class' => ['module-link', 'module-link-permissions'], 'title' => $this->t('Configure permissions')]],
+      ];
+    }
+
+    // Generate link for module's configuration page, if it has one.
+    if ($module->status && isset($module->info['configure'])) {
+      $route_parameters = isset($module->info['configure_parameters']) ? $module->info['configure_parameters'] : [];
+      if ($this->accessManager->checkNamedRoute($module->info['configure'], $route_parameters, $this->currentUser)) {
+        $row['links']['configure'] = [
+          '#type' => 'link',
+          '#title' => $this->t('Configure <span class="visually-hidden">the @module module</span>', ['@module' => $module->info['name']]),
+          '#url' => Url::fromRoute($module->info['configure'], $route_parameters),
+          '#options' => [
+            'attributes' => [
+              'class' => ['module-link', 'module-link-configure'],
+            ],
+          ],
+        ];
+      }
+    }
+
+    // If this module requires other modules, add them to the array.
+    foreach ($module->requires as $dependency => $version) {
+      if (!isset($modules[$dependency])) {
+        $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">missing</span>)', ['@module' => Unicode::ucfirst($dependency)]);
+      }
+      // Only display visible modules.
+      elseif (empty($modules[$dependency]->hidden)) {
+        $name = $modules[$dependency]->info['name'];
+        // Disable the module's checkbox if it is incompatible with the
+        // dependency's version.
+        if ($incompatible_version = drupal_check_incompatibility($version, str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> version @version)', [
+            '@module' => $name . $incompatible_version,
+            '@version' => $modules[$dependency]->info['version'],
+          ]);
+        }
+        // Disable the checkbox if the dependency is incompatible with this
+        // version of Drupal core.
+        elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', [
+            '@module' => $name,
+          ]);
+        }
+        elseif ($modules[$dependency]->status) {
+          $row['#requires'][$dependency] = $this->t('@module', ['@module' => $name]);
+        }
+        else {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-disabled">disabled</span>)', ['@module' => $name]);
+        }
+      }
+    }
+
+    // If this module is required by other modules, list those, and then make it
+    // impossible to disable this one.
+    foreach ($module->required_by as $dependent => $version) {
+      if (isset($modules[$dependent]) && empty($modules[$dependent]->info['hidden'])) {
+        if ($modules[$dependent]->status == 1 && $module->status == 1) {
+          $row['#required_by'][$dependent] = $this->t('@module', ['@module' => $modules[$dependent]->info['name']]);
+        }
+        else {
+          $row['#required_by'][$dependent] = $this->t('@module (<span class="admin-disabled">disabled</span>)', ['@module' => $modules[$dependent]->info['name']]);
+        }
+      }
+    }
+
+    return $row;
+  }
+
+}
diff --git a/core/modules/system/src/Form/ModulesListConfirmForm.php b/core/modules/system/src/Form/ModulesListConfirmForm.php
index 912a4327fd..028c4a20b7 100644
--- a/core/modules/system/src/Form/ModulesListConfirmForm.php
+++ b/core/modules/system/src/Form/ModulesListConfirmForm.php
@@ -202,7 +202,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       ]));
     }
 
-    $form_state->setRedirectUrl($this->getCancelUrl());
+    $form_state->setRedirectUrl(Url::fromRoute('system.modules_installed'));
   }
 
 }
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 3ff6c3d09d..e6538e7972 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -144,11 +144,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     // Sort all modules by their names.
     $modules = system_rebuild_module_data();
-    uasort($modules, 'system_sort_modules_by_info_name');
+    // Only list modules you can install.
+    $modules_to_list = array_filter($modules, function (Extension $module) {
+      return $module->status == FALSE;
+    });
+    uasort($modules_to_list, 'system_sort_modules_by_info_name');
 
     // Iterate over each of the modules.
     $form['modules']['#tree'] = TRUE;
-    foreach ($modules as $filename => $module) {
+    foreach ($modules_to_list as $filename => $module) {
       if (empty($module->info['hidden'])) {
         $package = $module->info['package'];
         $form['modules'][$package][$filename] = $this->buildRow($modules, $module, $distribution);
@@ -454,6 +458,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           '%name' => $module_names[0],
           '%names' => implode(', ', $module_names),
         ]));
+        // On successful install redirect to the installed modules list.
+        $form_state->setRedirectUrl(Url::fromRoute('system.modules_installed'));
       }
       catch (PreExistingConfigException $e) {
         $config_objects = $e->flattenConfigObjects($e->getConfigObjects());
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 799869bbc3..4ef8c0a0ed 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -183,6 +183,63 @@ function template_preprocess_system_modules_details(&$variables) {
   }
 }
 
+/**
+ * Prepares variables for the module installed templates.
+ *
+ * Default template: system-modules-installed.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - list: A render element representing the list. The main element represents
+ *     a package, and child elements of the package are individual projects.
+ *     Each project (or module) is an associative array containing the following
+ *     elements:
+ *     - name: The name of the module.
+ *     - description: A description of the module.
+ *     - version: The version of the module.
+ *     - links: Administration links provided by the module.
+ *     - #requires: A list of modules that the project requires.
+ *     - #required_by: A list of modules that require the project.
+ *     - #attributes: A list of attributes for the module wrapper.
+ *
+ * @see \Drupal\system\Controller\ModulesListInstalledController::listModules()
+ */
+function template_preprocess_system_modules_installed(&$variables) {
+  $list = $variables['list'];
+
+  $variables['modules'] = [];
+  // Iterate through all the modules, which are children of this element.
+  foreach (Element::children($list) as $key) {
+    // Stick the key into $module for easier access.
+    $module = $list[$key];
+    $module['#requires'] = array_filter($module['#requires']);
+    $module['#required_by'] = array_filter($module['#required_by']);
+
+    // Add the module label and expand/collapse functionality.
+    $id = Html::getUniqueId('module-' . $key);
+    $module['id'] = $id;
+
+    $module['machine_name'] = $key;
+
+    if (!empty($module['#requires'])) {
+      $module['requires'] = [
+        '#theme' => 'item_list',
+        '#items' => $module['#requires'],
+        '#context' => ['list_style' => 'comma-list'],
+      ];
+    }
+    if (!empty($module['#required_by'])) {
+      $module['required_by'] = [
+        '#theme' => 'item_list',
+        '#items' => $module['#required_by'],
+        '#context' => ['list_style' => 'comma-list'],
+      ];
+    }
+
+    $variables['modules'][] = $module;
+  }
+}
+
 /**
  * Prepares variables for module uninstall templates.
  *
diff --git a/core/modules/system/system.links.task.yml b/core/modules/system/system.links.task.yml
index db8f8564d8..426e5728f0 100644
--- a/core/modules/system/system.links.task.yml
+++ b/core/modules/system/system.links.task.yml
@@ -37,7 +37,11 @@ system.theme_settings_theme:
 system.modules_list:
   route_name: system.modules_list
   base_route: system.modules_list
-  title: 'List'
+  title: 'Available'
+system.modules_installed:
+  route_name: system.modules_installed
+  base_route: system.modules_list
+  title: 'Installed'
 system.modules_uninstall:
   route_name: system.modules_uninstall
   base_route: system.modules_list
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index bb82d4b19f..b7719d250a 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -219,6 +219,10 @@ function system_theme() {
       'render element' => 'form',
       'file' => 'system.admin.inc',
     ],
+    'system_modules_installed' => [
+      'render element' => 'list',
+      'file' => 'system.admin.inc',
+    ],
     'status_report_page' => [
       'variables' => [
         'counters' => [],
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 9cfe2ca54c..76cf203ac9 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -273,6 +273,14 @@ system.modules_list:
   requirements:
     _permission: 'administer modules'
 
+system.modules_installed:
+  path: '/admin/modules/installed'
+  defaults:
+    _controller: 'Drupal\system\Controller\ModulesListInstalledController::listModules'
+    _title: 'Installed'
+  requirements:
+    _permission: 'administer modules'
+
 system.modules_list_confirm:
   path: '/admin/modules/list/confirm'
   defaults:
diff --git a/core/modules/system/templates/system-modules-installed.html.twig b/core/modules/system/templates/system-modules-installed.html.twig
new file mode 100644
index 0000000000..447b506cfa
--- /dev/null
+++ b/core/modules/system/templates/system-modules-installed.html.twig
@@ -0,0 +1,70 @@
+{#
+/**
+ * @file
+ * Default theme implementation for the modules listing page.
+ *
+ * Displays a list of all packages in a project.
+ *
+ * Available variables:
+ * - modules: Contains multiple module instances. Each module contains:
+ *   - attributes: Attributes on the row.
+ *   - name: The human-readable name of the module.
+ *   - id: A unique identifier for interacting with the details element.
+ *   - description: The description of the module.
+ *   - machine_name: The module's machine name.
+ *   - version: Information about the module version.
+ *   - requires: A list of modules that this module requires.
+ *   - required_by: A list of modules that require this module.
+ *   - links: A list of administration links provided by the module.
+ *
+ * @see template_preprocess_system_modules_details()
+ *
+ * @ingroup themeable
+ */
+#}
+<table class="responsive-enabled">
+  <thead>
+    <tr>
+      <th class="name visually-hidden">{{ 'Name'|t }}</th>
+      <th class="description visually-hidden priority-low">{{ 'Description'|t }}</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for module in modules %}
+      {% set zebra = cycle(['odd', 'even'], loop.index0) %}
+      <tr class="{{ zebra }}">
+        <td class="module">
+          <label id="{{ module.id }}" class="module-name table-filter-text-source">{{ module.name }}</label>
+        </td>
+        <td class="description expand priority-low">
+          <details class="js-form-wrapper form-wrapper" id="{{ module.id }}-description">
+            <summary aria-controls="{{ module.id }}-description" role="button" aria-expanded="false"><span class="text module-description">{{ module.description }}</span></summary>
+            <div class="details-wrapper">
+              <div class="details-description">
+                <div class="requirements">
+                  <div class="admin-requirements">{{ 'Machine name: <span dir="ltr" class="table-filter-text-source">@machine-name</span>'|t({'@machine-name': module.machine_name }) }}</div>
+                  {% if module.version %}
+                    <div class="admin-requirements">{{ 'Version: @module-version'|t({'@module-version': module.version|render }) }}</div>
+                  {% endif %}
+                  {% if module.requires %}
+                    <div class="admin-requirements">{{ 'Requires: @module-list'|t({'@module-list': module.requires|render }) }}</div>
+                  {% endif %}
+                  {% if module.required_by %}
+                    <div class="admin-requirements">{{ 'Required by: @module-list'|t({'@module-list': module.required_by|render }) }}</div>
+                  {% endif %}
+                </div>
+                {% if module.links %}
+                  <div class="links">
+                    {% for link_type in ['help', 'permissions', 'configure'] %}
+                      {{ module.links[link_type] }}
+                    {% endfor %}
+                  </div>
+                {% endif %}
+              </div>
+            </div>
+          </details>
+        </td>
+      </tr>
+    {% endfor %}
+  </tbody>
+</table>
\ No newline at end of file
diff --git a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
index dc63e64d72..2b0ad0a8df 100644
--- a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
+++ b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
@@ -14,7 +14,7 @@ class ModulesListFormWebTest extends BrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = ['system_test', 'help'];
+  public static $modules = ['system_test', 'help', 'dblog'];
 
   /**
    * {@inheritdoc}
@@ -33,7 +33,7 @@ public function testModuleListForm() {
         ['administer modules', 'administer permissions']
       )
     );
-    $this->drupalGet('admin/modules');
+    $this->drupalGet('admin/modules/installed');
     $this->assertResponse('200');
 
     // Check that system_test's configure link was rendered correctly.
@@ -45,10 +45,10 @@ public function testModuleListForm() {
     // Check that system_test's help link was rendered correctly.
     $this->assertFieldByXPath("//a[contains(@href, '/admin/help/system_test') and @title='Help']");
 
-    // Ensure that the Testing module's machine name is printed. Testing module
-    // is used because its machine name is different than its human readable
-    // name.
-    $this->assertText('simpletest');
+    // Ensure that the Database logging module's machine name is printed.
+    // Database logging module is used because its machine name is different
+    // than its human readable name.
+    $this->assertText('dblog');
   }
 
 }
diff --git a/core/modules/system/tests/src/Functional/Module/RequiredTest.php b/core/modules/system/tests/src/Functional/Module/RequiredTest.php
deleted file mode 100644
index 00c7cbe703..0000000000
--- a/core/modules/system/tests/src/Functional/Module/RequiredTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-namespace Drupal\Tests\system\Functional\Module;
-
-/**
- * Attempt disabling of required modules.
- *
- * @group Module
- */
-class RequiredTest extends ModuleTestBase {
-  /**
-   * Assert that core required modules cannot be disabled.
-   */
-  public function testDisableRequired() {
-    $module_info = system_get_info('module');
-    $this->drupalGet('admin/modules');
-    foreach ($module_info as $module => $info) {
-      // Check to make sure the checkbox for each required module is disabled
-      // and checked (or absent from the page if the module is also hidden).
-      if (!empty($info['required'])) {
-        $field_name = 'modules[' . $module . '][enable]';
-        if (empty($info['hidden'])) {
-          $this->assertFieldByXPath("//input[@name='$field_name' and @disabled='disabled' and @checked='checked']", '', format_string('Field @name was disabled and checked.', ['@name' => $field_name]));
-        }
-        else {
-          $this->assertNoFieldByName($field_name);
-        }
-      }
-    }
-  }
-
-}
diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
index 2b17b8fcf6..32a9517332 100644
--- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php
+++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
@@ -57,6 +57,17 @@ public function testUninstallPage() {
     $this->drupalGet('admin/modules/uninstall');
     $this->assertTitle(t('Uninstall') . ' | Drupal');
 
+    foreach (\Drupal::service('extension.list.module')->getAllInstalledInfo() as $module => $info) {
+      $field_name = "uninstall[$module]";
+      if (!empty($info['required'])) {
+        // A required module should not be listed on the uninstall page..
+        $this->assertSession()->fieldNotExists($field_name);
+      }
+      else {
+        $this->assertSession()->fieldExists($field_name);
+      }
+    }
+
     // Be sure labels are rendered properly.
     // @see regression https://www.drupal.org/node/2512106
     $this->assertRaw('<label for="edit-uninstall-node" class="module-name table-filter-text-source">Node</label>');
diff --git a/core/themes/stable/css/system/system.admin.css b/core/themes/stable/css/system/system.admin.css
index 24b41dc20e..3506634079 100644
--- a/core/themes/stable/css/system/system.admin.css
+++ b/core/themes/stable/css/system/system.admin.css
@@ -138,6 +138,12 @@ small .admin-link:after {
   padding-left: 12px;
   padding-right: 0;
 }
+.system-modules-installed td {
+  padding-left: inherit; /* LTR */
+}
+[dir="rtl"] .system-modules-installed td {
+  padding-right: inherit;
+}
 
 @media screen and (max-width: 40em) {
   .system-modules td.name {
diff --git a/core/themes/stable/templates/admin/system-modules-installed.html.twig b/core/themes/stable/templates/admin/system-modules-installed.html.twig
new file mode 100644
index 0000000000..447b506cfa
--- /dev/null
+++ b/core/themes/stable/templates/admin/system-modules-installed.html.twig
@@ -0,0 +1,70 @@
+{#
+/**
+ * @file
+ * Default theme implementation for the modules listing page.
+ *
+ * Displays a list of all packages in a project.
+ *
+ * Available variables:
+ * - modules: Contains multiple module instances. Each module contains:
+ *   - attributes: Attributes on the row.
+ *   - name: The human-readable name of the module.
+ *   - id: A unique identifier for interacting with the details element.
+ *   - description: The description of the module.
+ *   - machine_name: The module's machine name.
+ *   - version: Information about the module version.
+ *   - requires: A list of modules that this module requires.
+ *   - required_by: A list of modules that require this module.
+ *   - links: A list of administration links provided by the module.
+ *
+ * @see template_preprocess_system_modules_details()
+ *
+ * @ingroup themeable
+ */
+#}
+<table class="responsive-enabled">
+  <thead>
+    <tr>
+      <th class="name visually-hidden">{{ 'Name'|t }}</th>
+      <th class="description visually-hidden priority-low">{{ 'Description'|t }}</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for module in modules %}
+      {% set zebra = cycle(['odd', 'even'], loop.index0) %}
+      <tr class="{{ zebra }}">
+        <td class="module">
+          <label id="{{ module.id }}" class="module-name table-filter-text-source">{{ module.name }}</label>
+        </td>
+        <td class="description expand priority-low">
+          <details class="js-form-wrapper form-wrapper" id="{{ module.id }}-description">
+            <summary aria-controls="{{ module.id }}-description" role="button" aria-expanded="false"><span class="text module-description">{{ module.description }}</span></summary>
+            <div class="details-wrapper">
+              <div class="details-description">
+                <div class="requirements">
+                  <div class="admin-requirements">{{ 'Machine name: <span dir="ltr" class="table-filter-text-source">@machine-name</span>'|t({'@machine-name': module.machine_name }) }}</div>
+                  {% if module.version %}
+                    <div class="admin-requirements">{{ 'Version: @module-version'|t({'@module-version': module.version|render }) }}</div>
+                  {% endif %}
+                  {% if module.requires %}
+                    <div class="admin-requirements">{{ 'Requires: @module-list'|t({'@module-list': module.requires|render }) }}</div>
+                  {% endif %}
+                  {% if module.required_by %}
+                    <div class="admin-requirements">{{ 'Required by: @module-list'|t({'@module-list': module.required_by|render }) }}</div>
+                  {% endif %}
+                </div>
+                {% if module.links %}
+                  <div class="links">
+                    {% for link_type in ['help', 'permissions', 'configure'] %}
+                      {{ module.links[link_type] }}
+                    {% endfor %}
+                  </div>
+                {% endif %}
+              </div>
+            </div>
+          </details>
+        </td>
+      </tr>
+    {% endfor %}
+  </tbody>
+</table>
\ No newline at end of file
