diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 3363efc..8231668 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -272,6 +272,7 @@ public function rebuildThemeData() {
       'screenshot' => 'screenshot.png',
       'php' => DRUPAL_MINIMUM_PHP,
       'libraries' => array(),
+      'dependencies' => array(),
     );
 
     $sub_themes = array();
@@ -358,6 +359,17 @@ public function rebuildThemeData() {
       }
     }
 
+    foreach ($themes as $key => $theme) {
+      // buildModuleDependencies() adds a theme->requires array that contains
+      // both module and base theme dependencies, if they are specified. Ensure
+      // that every theme stores the list of module dependencies separately
+      // from the full requires list.
+      if (!isset($theme->requires)) {
+        $theme->requires = [];
+      }
+      $themes[$key]->module_dependencies = isset($theme->base_themes) ? array_diff_key($theme->requires, $theme->base_themes) : $theme->requires;
+    }
+
     return $themes;
   }
 
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
index 99de386..e12ff0b 100644
--- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\ConfigInstallerInterface;
 use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Extension\ModuleInstallerInterface;
 use Drupal\Core\Routing\RouteBuilderInterface;
 use Drupal\Core\State\StateInterface;
 use Psr\Log\LoggerInterface;
@@ -56,6 +57,11 @@ class ThemeInstaller implements ThemeInstallerInterface {
    */
   protected $logger;
 
+  /**
+   * @var \Drupal\Core\Extension\ModuleInstallerInterface|NULL
+   */
+  protected $moduleInstaller;
+
 
   /**
    * Constructs a new ThemeInstaller.
@@ -81,8 +87,10 @@ class ThemeInstaller implements ThemeInstallerInterface {
    *   A logger instance.
    * @param \Drupal\Core\State\StateInterface $state
    *   The state store.
+   * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
+   *   The module installer.
    */
-  public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state) {
+  public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state, ModuleInstallerInterface $module_installer = NULL) {
     $this->themeHandler = $theme_handler;
     $this->configFactory = $config_factory;
     $this->configInstaller = $config_installer;
@@ -92,6 +100,17 @@ public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryI
     $this->routeBuilder = $route_builder;
     $this->logger = $logger;
     $this->state = $state;
+    $this->moduleInstaller = $module_installer;
+  }
+
+  /**
+   * @return \Drupal\Core\Extension\ModuleInstallerInterface
+   */
+  protected function getModuleInstaller() {
+    if (!isset($this->moduleInstaller)) {
+      $this->moduleInstaller = \Drupal::service('module_installer');
+    }
+    return $this->moduleInstaller;
   }
 
   /**
@@ -118,9 +137,15 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
       }
 
       while (list($theme) = each($theme_list)) {
-        // Add dependencies to the list. The new themes will be processed as
-        // the while loop continues.
-        foreach (array_keys($theme_data[$theme]->requires) as $dependency) {
+        $module_dependencies = $theme_data[$theme]->module_dependencies;
+        $theme_dependencies = array_diff_key($theme_data[$theme]->requires, $module_dependencies);
+
+        // Install the module dependencies.
+        $this->getModuleInstaller()->install(array_keys($module_dependencies));
+
+        // Add dependencies to the list of themes to install. The new themes
+        // will be processed as the while loop continues.
+        foreach (array_keys($theme_dependencies) as $dependency) {
           if (!isset($theme_data[$dependency])) {
             // The dependency does not exist.
             return FALSE;
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index 98d6179..d6c0f05 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Theme\ThemeAccessCheck;
 use Drupal\Core\Url;
+use Drupal\system\Form\ModulesListForm;
 use Drupal\system\SystemManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -202,6 +203,7 @@ public function themesPage() {
     $theme_groups  = array('installed' => array(), 'uninstalled' => array());
     $admin_theme = $config->get('admin');
     $admin_theme_options = array();
+    $modules = array();
 
     foreach ($themes as &$theme) {
       if (!empty($theme->info['hidden'])) {
@@ -244,9 +246,37 @@ public function themesPage() {
         $theme->incompatible_base = (isset($theme->info['base theme']) && !($theme->base_themes === array_filter($theme->base_themes)));
         // Confirm that the theme engine is available.
         $theme->incompatible_engine = isset($theme->info['engine']) && !isset($theme->owner);
+        // Confirm that module dependencies are available.
+        $theme->incompatible_module = FALSE;
       }
+
+      // Check module dependencies.
+      if ($theme->module_dependencies) {
+        if (empty($modules)) {
+          $modules = system_rebuild_module_data();
+        }
+        foreach ($theme->module_dependencies as $dependency => $version) {
+          if ($incompatible = ModulesListForm::checkDependency($modules, $dependency, $version)) {
+            $theme->module_dependencies[$dependency] = $incompatible;
+            $theme->incompatible_module = TRUE;
+          }
+          // Only display visible modules.
+          elseif (!empty($modules[$dependency]->hidden)) {
+            unset($theme->module_dependencies[$dependency]);
+          } else {
+            $name = $modules[$dependency]->info['name'];
+            if ($modules[$dependency]->status) {
+              $theme->module_dependencies[$dependency] = $this->t('@module', array('@module' => $name));
+            }
+            else {
+              $theme->module_dependencies[$dependency] = $this->t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $name));
+            }
+          }
+        }
+      }
+
       $theme->operations = array();
-      if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) {
+      if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine && !$theme->incompatible_module) {
         // Create the operations links.
         $query['theme'] = $theme->getName();
         if ($this->themeAccess->checkAccess($theme->getName())) {
diff --git a/core/modules/system/src/Controller/ThemeController.php b/core/modules/system/src/Controller/ThemeController.php
index e135356..62e0b5e 100644
--- a/core/modules/system/src/Controller/ThemeController.php
+++ b/core/modules/system/src/Controller/ThemeController.php
@@ -107,8 +107,22 @@ public function install(Request $request) {
     if (isset($theme)) {
       try {
         if ($this->themeHandler->install(array($theme))) {
-          $themes = $this->themeHandler->listInfo();
-          drupal_set_message($this->t('The %theme theme has been installed.', array('%theme' => $themes[$theme]->info['name'])));
+          $theme_data = $this->themeHandler->listInfo();
+          if ($theme_data[$theme]->module_dependencies) {
+            $module_data = system_rebuild_module_data();
+            $module_names = [];
+            foreach(array_keys($theme_data[$theme]->module_dependencies) as $key) {
+              $module_names[] = $module_data[$key]->info['name'];
+            }
+            drupal_set_message($this->formatPlural(count($module_names), 'The %theme theme and its module dependency, %name, have been installed.', 'The %theme theme and its @count module dependencies have been installed: %names', array(
+              '%theme' => $theme_data[$theme]->info['name'],
+              '%name' => $module_names[0],
+              '%names' => implode(', ', $module_names),
+            )));
+          }
+          else {
+            drupal_set_message($this->t('The %theme theme has been installed.', array('%theme' => $theme_data[$theme]->info['name'])));
+          }
         }
         else {
           drupal_set_message($this->t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 7831c38..30b5fd4 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -187,10 +187,48 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   }
 
   /**
+   * Checks a single module dependency of a module or theme that has the given
+   * module version requirements.
+   *
+   * @param array $modules
+   *   The list of existing modules.
+   * @param string $dependency
+   *   The dependency to check.
+   * @param array $version
+   *   Version requirement data from the module or theme declaring the
+   *   dependency we are checking.
+   *
+   * @return string|null
+   *   NULL if compatible, otherwise a string describing the incompatibility.
+   */
+  public static function checkDependency(array $modules, $dependency, $version) {
+    if (!isset($modules[$dependency])) {
+      return t('@module (<span class="admin-missing">missing</span>)', array('@module' => Unicode::ucfirst($dependency)));
+    }
+    else {
+      $name = $modules[$dependency]->info['name'];
+      // Check 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']))) {
+        return t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
+          '@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) {
+        return t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', array(
+          '@module' => $name,
+        ));
+      }
+    }
+  }
+
+  /**
    * Builds a table row for the system modules page.
    *
    * @param array $modules
-   *   The list existing modules.
+   *   The list of existing modules.
    * @param \Drupal\Core\Extension\Extension $module
    *   The module for which to build the form row.
    * @param $distribution
@@ -297,35 +335,20 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
 
     // 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>)', array('@module' => Unicode::ucfirst($dependency)));
-        $row['enable']['#disabled'] = TRUE;
-      }
-      // 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)', array(
-            '@module' => $name . $incompatible_version,
-            '@version' => $modules[$dependency]->info['version'],
-          ));
+      // Only display missing or visible modules.
+      if (empty($modules[$dependency]->hidden)) {
+        if ($incompatible = $this->checkDependency($modules, $dependency, $version)) {
+          $row['#requires'][$dependency] = $incompatible;
           $row['enable']['#disabled'] = TRUE;
         }
-        // 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)', array(
-            '@module' => $name,
-          ));
-          $row['enable']['#disabled'] = TRUE;
-        }
-        elseif ($modules[$dependency]->status) {
-          $row['#requires'][$dependency] = $this->t('@module', array('@module' => $name));
-        }
         else {
-          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $name));
+          $name = $modules[$dependency]->info['name'];
+          if ($modules[$dependency]->status) {
+            $row['#requires'][$dependency] = $this->t('@module', array('@module' => $name));
+          }
+          else {
+            $row['#requires'][$dependency] = $this->t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $name));
+          }
         }
       }
     }
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index f9c49c7..d050ecd 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -225,6 +225,7 @@ function template_preprocess_system_modules_details(&$variables) {
       ];
       $module['requires'] = $renderer->render($requires);
     }
+    // @TODO: Add theme dependencies.
     if (!empty($module['#required_by'])) {
       $required_by = [
         '#theme' => 'item_list',
@@ -350,6 +351,15 @@ function template_preprocess_system_themes_page(&$variables) {
       $current_theme['is_default'] = $theme->is_default;
       $current_theme['is_admin'] = $theme->is_admin;
 
+      $current_theme['requires'] = '';
+      if (!empty($theme->module_dependencies)) {
+        $current_theme['requires'] = [
+          '#theme' => 'item_list',
+          '#items' => $theme->module_dependencies,
+          '#context' => ['list_style' => 'comma-list'],
+        ];
+      }
+
       // Make sure to provide feedback on compatibility.
       $current_theme['incompatible'] = '';
       if (!empty($theme->incompatible_core)) {
@@ -370,6 +380,9 @@ function template_preprocess_system_themes_page(&$variables) {
       elseif (!empty($theme->incompatible_engine)) {
         $current_theme['incompatible'] = t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => $theme->info['engine']));
       }
+      elseif (!empty($theme->incompatible_module)) {
+        $current_theme['incompatible'] = t('This theme requires the listed modules to operate correctly.');
+      }
 
       // Build operation links.
       $current_theme['operations'] = array(
diff --git a/core/modules/system/templates/system-themes-page.html.twig b/core/modules/system/templates/system-themes-page.html.twig
index 6e65d76..45b4930 100644
--- a/core/modules/system/templates/system-themes-page.html.twig
+++ b/core/modules/system/templates/system-themes-page.html.twig
@@ -22,6 +22,7 @@
  *     - notes: Identifies what context this theme is being used in, e.g.,
  *       default theme, admin theme.
  *     - incompatible: Text describing any compatibility issues.
+ *     - requires: A list of modules that this theme requires.
  *     - operations: A list of operation links, e.g., Settings, Enable, Disable,
  *       etc. these links should only be displayed if the theme is compatible.
  *
@@ -62,6 +63,9 @@
               {%- endif -%}
             </h3>
             <div class="theme-info__description">{{ theme.description }}</div>
+            {% if theme.requires %}
+              <div class="theme-info__requires">Requires: {{ theme.requires }}</div>
+            {% endif %}
             {# Display operation links if the theme is compatible. #}
             {% if theme.incompatible %}
               <div class="incompatible">{{ theme.incompatible }}</div>
diff --git a/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/src/Service.php b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/src/Service.php
new file mode 100644
index 0000000..3e7d7ee
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/src/Service.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\test_module_required_by_theme;
+
+class Service {
+
+}
diff --git a/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.info.yml b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.info.yml
new file mode 100644
index 0000000..9d402c7
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.info.yml
@@ -0,0 +1,5 @@
+name: test module required by theme
+type: module
+core: 8.x
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.services.yml b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.services.yml
new file mode 100644
index 0000000..8be2053
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_depending_on_module/test_module_required_by_theme/test_module_required_by_theme.services.yml
@@ -0,0 +1,3 @@
+services:
+  test_module_required_by_theme.service:
+    class: Drupal\test_module_required_by_theme\Service
diff --git a/core/modules/system/tests/themes/test_theme_depending_on_module/test_theme_depending_on_module.info.yml b/core/modules/system/tests/themes/test_theme_depending_on_module/test_theme_depending_on_module.info.yml
new file mode 100644
index 0000000..37e9b9b
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_depending_on_module/test_theme_depending_on_module.info.yml
@@ -0,0 +1,5 @@
+name: test theme depending on module
+type: theme
+core: 8.x
+dependencies:
+  - test_module_required_by_theme
diff --git a/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php
index a77adfc..29792d1 100644
--- a/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Asset/LibraryDiscoveryIntegrationTest.php
@@ -14,6 +14,11 @@
 class LibraryDiscoveryIntegrationTest extends KernelTestBase {
 
   /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system'];
+
+  /**
    * The library discovery service.
    *
    * @var \Drupal\Core\Asset\LibraryDiscoveryInterface
diff --git a/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php
index cd33fcb..512a3bd 100644
--- a/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Render/ElementInfoIntegrationTest.php
@@ -14,6 +14,11 @@ class ElementInfoIntegrationTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
+  public static $modules = ['system'];
+
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     parent::setUp();
 
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
index b82978c..5cda7a9 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
@@ -5,6 +5,7 @@
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Extension\ExtensionNameLengthException;
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\test_theme_dependency\Service;
 
 /**
  * Tests installing and uninstalling of themes.
@@ -350,6 +351,15 @@ function testThemeInfoAlter() {
     $this->assertFalse(isset($system_list[$name]->info['regions']['test_region']));
   }
 
+  public function testThemeWithModuleDependency() {
+    $this->assertFalse($this->moduleHandler()->moduleExists('test_module_required_by_theme'));
+    $this->themeInstaller()->install(['test_theme_depending_on_module']);
+    $this->assertTrue($this->moduleHandler()->moduleExists('test_module_required_by_theme'));
+
+    $service = \Drupal::service('test_module_required_by_theme.service');
+    $this->assertInstanceOf(\Drupal\test_module_required_by_theme\Service::class, $service);
+  }
+
   /**
    * Returns the theme handler service.
    *
diff --git a/core/themes/stable/templates/admin/system-themes-page.html.twig b/core/themes/stable/templates/admin/system-themes-page.html.twig
index 5a23f1a..0bc0439 100644
--- a/core/themes/stable/templates/admin/system-themes-page.html.twig
+++ b/core/themes/stable/templates/admin/system-themes-page.html.twig
@@ -22,6 +22,7 @@
  *     - notes: Identifies what context this theme is being used in, e.g.,
  *       default theme, admin theme.
  *     - incompatible: Text describing any compatibility issues.
+ *     - requires: A list of modules that this theme requires.
  *     - operations: A list of operation links, e.g., Settings, Enable, Disable,
  *       etc. these links should only be displayed if the theme is compatible.
  *
@@ -60,6 +61,9 @@
               {%- endif -%}
             </h3>
             <div class="theme-info__description">{{ theme.description }}</div>
+            {% if theme.requires %}
+              <div class="theme-info__requires">Requires: {{ theme.requires }}</div>
+            {% endif %}
             {# Display operation links if the theme is compatible. #}
             {% if theme.incompatible %}
               <div class="incompatible">{{ theme.incompatible }}</div>
