diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 55ec85f..2caf6da 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -895,17 +895,16 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) {
  * process functions for them would incur a noticeable performance penalty.
  *
  * @subsection sub_alternate_suggestions Suggesting Alternate Hooks
- * There are two special variables that these preprocess and process functions
- * can set: 'theme_hook_suggestion' and 'theme_hook_suggestions'. These will be
- * merged together to form a list of 'suggested' alternate theme hooks to use,
- * in reverse order of priority. theme_hook_suggestion will always be a higher
- * priority than items in theme_hook_suggestions. theme() will use the
- * highest priority implementation that exists. If none exists, theme() will
- * use the implementation for the theme hook it was called with. These
- * suggestions are similar to and are used for similar reasons as calling
- * theme() with an array as the $hook parameter (see below). The difference
- * is whether the suggestions are determined by the code that calls theme() or
- * by a preprocess or process function.
+ * Alternate hooks can be suggested by implementing the hook-specific
+ * hook_theme_suggestions_HOOK_alter() or the generic
+ * hook_theme_suggestions_alter(). These alter hooks are used to manipulate an
+ * array of suggested alternate theme hooks to use, in reverse order of
+ * priority. theme() will use the highest priority implementation that exists.
+ * If none exists, theme() will use the implementation for the theme hook it was
+ * called with. These suggestions are similar to and are used for similar
+ * reasons as calling theme() with an array as the $hook parameter (see below).
+ * The difference is whether the suggestions are determined by the code that
+ * calls theme() or by altering the suggestions via the suggestion alter hooks.
  *
  * @param $hook
  *   The name of the theme hook to call. If the name contains a
@@ -1036,11 +1035,27 @@ function theme($hook, $variables = array()) {
     'theme_hook_original' => $original_hook,
   );
 
-  // Invoke the variable processors, if any. The processors may specify
-  // alternate suggestions for which hook's template/function to use. If the
-  // hook is a suggestion of a base hook, invoke the variable processors of
-  // the base hook, but retain the suggestion as a high priority suggestion to
-  // be used unless overridden by a variable processor function.
+  // Invoke theme hook suggestion alter hooks.
+  $suggestions = array();
+  $suggestion_hooks = array(
+    'theme_suggestions',
+    'theme_suggestions_' . $hook,
+  );
+  Drupal::moduleHandler()->alter($suggestion_hooks, $suggestions, $variables, $hook);
+
+  // Check if each suggestion exists in the theme registry, and if so,
+  // use it instead of the hook that theme() was called with. For example, a
+  // function may call theme('node', ...), but a module can add
+  // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
+  // enabling a theme to have an alternate template file for article nodes.
+  foreach (array_reverse($suggestions) as $suggestion) {
+    if (isset($hooks[$suggestion])) {
+      $info = $hooks[$suggestion];
+      break;
+    }
+  }
+
+  // Invoke the variable processors, if any.
   if (isset($info['base hook'])) {
     $base_hook = $info['base hook'];
     $base_hook_info = $hooks[$base_hook];
@@ -1051,14 +1066,15 @@ function theme($hook, $variables = array()) {
         include_once DRUPAL_ROOT . '/' . $include_file;
       }
     }
-    if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) {
-      $variables['theme_hook_suggestion'] = $hook;
-      $hook = $base_hook;
-      $info = $base_hook_info;
+    // Add preprocess and process functions from the base hook to the $info
+    // array.
+    foreach (array('preprocess functions', 'process functions') as $processor) {
+      if (isset($base_hook_info[$processor])) {
+        $info[$processor] = $base_hook_info[$processor];
+      }
     }
   }
   if (isset($info['preprocess functions']) || isset($info['process functions'])) {
-    $variables['theme_hook_suggestions'] = array();
     foreach (array('preprocess functions', 'process functions') as $phase) {
       if (!empty($info[$phase])) {
         foreach ($info[$phase] as $processor_function) {
@@ -1068,31 +1084,6 @@ function theme($hook, $variables = array()) {
         }
       }
     }
-    // If the preprocess/process functions specified hook suggestions, and the
-    // suggestion exists in the theme registry, use it instead of the hook that
-    // theme() was called with. This allows the preprocess/process step to
-    // route to a more specific theme hook. For example, a function may call
-    // theme('node', ...), but a preprocess function can add 'node__article' as
-    // a suggestion, enabling a theme to have an alternate template file for
-    // article nodes. Suggestions are checked in the following order:
-    // - The 'theme_hook_suggestion' variable is checked first. It overrides
-    //   all others.
-    // - The 'theme_hook_suggestions' variable is checked in FILO order, so the
-    //   last suggestion added to the array takes precedence over suggestions
-    //   added earlier.
-    $suggestions = array();
-    if (!empty($variables['theme_hook_suggestions'])) {
-      $suggestions = $variables['theme_hook_suggestions'];
-    }
-    if (!empty($variables['theme_hook_suggestion'])) {
-      $suggestions[] = $variables['theme_hook_suggestion'];
-    }
-    foreach (array_reverse($suggestions) as $suggestion) {
-      if (isset($hooks[$suggestion])) {
-        $info = $hooks[$suggestion];
-        break;
-      }
-    }
   }
 
   // Generate the output using either a function or a template.
@@ -1155,7 +1146,7 @@ function theme($hook, $variables = array()) {
     if (isset($info['path'])) {
       $template_file = $info['path'] . '/' . $template_file;
     }
-    $output = $render_function($template_file, $variables);
+    $output = $render_function($template_file, $variables, $suggestions);
   }
 
   // restore path_to_theme()
@@ -2676,11 +2667,6 @@ function template_preprocess_html(&$variables) {
     drupal_add_html_head($element, $name);
   }
 
-  // Populate the page template suggestions.
-  if ($suggestions = theme_get_suggestions(arg(), 'html')) {
-    $variables['theme_hook_suggestions'] = $suggestions;
-  }
-
   drupal_add_library('system', 'html5shiv', TRUE);
 
   // Render page_top and page_bottom into top level variables.
@@ -2791,11 +2777,6 @@ function template_preprocess_page(&$variables) {
     $variables['node'] = $node;
   }
 
-  // Populate the page template suggestions.
-  if ($suggestions = theme_get_suggestions(arg(), 'page')) {
-    $variables['theme_hook_suggestions'] = $suggestions;
-  }
-
   // Prepare render array for messages. drupal_get_messages() is called later,
   // when this variable is rendered in a theme function or template file.
   $variables['messages'] = array(
@@ -2832,9 +2813,10 @@ function template_process_page(&$variables) {
 /**
  * Generate an array of suggestions from path arguments.
  *
- * This is typically called for adding to the 'theme_hook_suggestions' or
- * 'attributes' class key variables from within preprocess functions, when
- * wanting to base the additional suggestions on the path of the current page.
+ * This is typically called for adding to the suggestions in
+ * hook_theme_suggestions_HOOK_alter() or adding to 'attributes' class key
+ * variables from within preprocess functions, when wanting to base the
+ * additional suggestions or classes on the path of the current page.
  *
  * @param $args
  *   An array of path arguments, such as from function arg().
@@ -2848,9 +2830,8 @@ function template_process_page(&$variables) {
  *
  * @return
  *   An array of suggestions, suitable for adding to
- *   $variables['theme_hook_suggestions'] within a preprocess function or to
- *   $variables['attributes']['class'] if the suggestions represent extra CSS
- *   classes.
+ *   hook_theme_suggestions_HOOK_alter() or to $variables['attributes']['class']
+ *   if the suggestions represent extra CSS classes.
  */
 function theme_get_suggestions($args, $base, $delimiter = '__') {
 
@@ -2997,12 +2978,6 @@ function template_preprocess_maintenance_page(&$variables) {
     $variables['attributes']['class'][] = 'sidebar-' . $variables['layout'];
   }
 
-  // Dead databases will show error messages so supplying this template will
-  // allow themers to override the page and the content completely.
-  if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
-    $variables['theme_hook_suggestion'] = 'maintenance_page__offline';
-  }
-
   $variables['head'] = drupal_get_html_head();
 
   // While this code is used in the installer, the language module may not be
@@ -3055,7 +3030,6 @@ function template_preprocess_region(&$variables) {
 
   $variables['attributes']['class'][] = 'region';
   $variables['attributes']['class'][] = drupal_html_class('region-' . $variables['region']);
-  $variables['theme_hook_suggestions'][] = 'region__' . $variables['region'];
 }
 
 /**
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index cfbad28..f0b4529 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -491,6 +491,35 @@ function block_rebuild() {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function block_theme_suggestions_block_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'block__' . $variables['elements']['#configuration']['module'];
+  // Hyphens (-) and underscores (_) play a special role in theme suggestions.
+  // Theme suggestions should only contain underscores, because within
+  // drupal_find_theme_templates(), underscores are converted to hyphens to
+  // match template file names, and then converted back to underscores to match
+  // pre-processing and other function names. So if your theme suggestion
+  // contains a hyphen, it will end up as an underscore after this conversion,
+  // and your function names won't be recognized. So, we need to convert
+  // hyphens to underscores in block deltas for the theme suggestions.
+
+  // We can safely explode on : because we know the Block plugin type manager
+  // enforces that delimiter for all derivatives.
+  $parts = explode(':', $variables['elements']['#plugin_id']);
+  $suggestion = 'block';
+  while ($part = array_shift($parts)) {
+    $suggestions[] = $suggestion .= '__' . strtr($part, '-', '_');
+  }
+
+  if ($id = $variables['elements']['#block']->id()) {
+    $config_id = explode('.', $id);
+    $machine_name = array_pop($config_id);
+    $suggestions[] = 'block__' . $machine_name;
+  }
+}
+
+/**
  * Prepares variables for block templates.
  *
  * Default template: block.html.twig.
@@ -533,29 +562,11 @@ function template_preprocess_block(&$variables) {
   // Add default class for block content.
   $variables['content_attributes']['class'][] = 'content';
 
-  $variables['theme_hook_suggestions'][] = 'block__' . $variables['configuration']['module'];
-  // Hyphens (-) and underscores (_) play a special role in theme suggestions.
-  // Theme suggestions should only contain underscores, because within
-  // drupal_find_theme_templates(), underscores are converted to hyphens to
-  // match template file names, and then converted back to underscores to match
-  // pre-processing and other function names. So if your theme suggestion
-  // contains a hyphen, it will end up as an underscore after this conversion,
-  // and your function names won't be recognized. So, we need to convert
-  // hyphens to underscores in block deltas for the theme suggestions.
-
-  // We can safely explode on : because we know the Block plugin type manager
-  // enforces that delimiter for all derivatives.
-  $parts = explode(':', $variables['plugin_id']);
-  $suggestion = 'block';
-  while ($part = array_shift($parts)) {
-    $variables['theme_hook_suggestions'][] = $suggestion .= '__' . strtr($part, '-', '_');
-  }
   // Create a valid HTML ID and make sure it is unique.
   if ($id = $variables['elements']['#block']->id()) {
     $config_id = explode('.', $id);
     $machine_name = array_pop($config_id);
     $variables['attributes']['id'] = drupal_html_id('block-' . $machine_name);
-    $variables['theme_hook_suggestions'][] = 'block__' . $machine_name;
   }
 }
 
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockPreprocessUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockPreprocessUnitTest.php
new file mode 100644
index 0000000..ddbc40c
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockPreprocessUnitTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Tests\BlockPreprocessUnitTest.
+ */
+
+namespace Drupal\block\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Unit tests for template_preprocess_block().
+ */
+class BlockPreprocessUnitTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Block preprocess',
+      'description' => 'Test the template_preprocess_block() function.',
+      'group' => 'Block',
+    );
+  }
+
+  /**
+   * Tests block classes with template_preprocess_block().
+   */
+  function testBlockClasses() {
+    // Define a block with a derivative to be preprocessed, which includes both
+    // an underscore (not transformed) and a hyphen (transformed to underscore),
+    // and generates possibilities for each level of derivative.
+    // @todo Clarify this comment.
+    $block = entity_create('block', array(
+      'plugin' => 'system_menu_block:menu-admin',
+      'region' => 'footer',
+      'id' => config('system.theme')->get('default') . '.machinename',
+    ));
+
+    $variables = array();
+    $variables['elements']['#block'] = $block;
+    $variables['elements']['#configuration'] = $block->getPlugin()->getConfig();
+    $variables['elements']['#plugin_id'] = $block->get('plugin');
+    $variables['elements']['content'] = array();
+    $variables['content_attributes']['class'][] = 'test-class';
+    template_preprocess_block($variables);
+    $this->assertEqual($variables['content_attributes']['class'], array('test-class', 'content'), 'Default .content class added to block content_attributes');
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
index 51630ba..67ce2ed 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
@@ -10,7 +10,7 @@
 use Drupal\simpletest\WebTestBase;
 
 /**
- * Unit tests for template_preprocess_block().
+ * Unit tests for block_theme_suggestions_block_alter().
  */
 class BlockTemplateSuggestionsUnitTest extends WebTestBase {
 
@@ -24,13 +24,13 @@ class BlockTemplateSuggestionsUnitTest extends WebTestBase {
   public static function getInfo() {
     return array(
       'name' => 'Block template suggestions',
-      'description' => 'Test the template_preprocess_block() function.',
+      'description' => 'Test the block_theme_suggestions_block_alter() function.',
       'group' => 'Block',
     );
   }
 
   /**
-   * Test if template_preprocess_block() handles the suggestions right.
+   * Tests template suggestions from block_theme_suggestions_block_alter().
    */
   function testBlockThemeHookSuggestions() {
     // Define a block with a derivative to be preprocessed, which includes both
@@ -43,16 +43,14 @@ function testBlockThemeHookSuggestions() {
       'id' => config('system.theme')->get('default') . '.machinename',
     ));
 
+    $suggestions = array();
     $variables = array();
     $variables['elements']['#block'] = $block;
     $variables['elements']['#configuration'] = $block->getPlugin()->getConfig();
     $variables['elements']['#plugin_id'] = $block->get('plugin');
     $variables['elements']['content'] = array();
-    // Test adding a class to the block content.
-    $variables['content_attributes']['class'][] = 'test-class';
-    template_preprocess_block($variables);
-    $this->assertEqual($variables['theme_hook_suggestions'], array('block__system', 'block__system_menu_block', 'block__system_menu_block__menu_admin', 'block__machinename'));
-    $this->assertEqual($variables['content_attributes']['class'], array('test-class', 'content'), 'Default .content class added to block content_attributes');
+    block_theme_suggestions_block_alter($suggestions, $variables);
+    $this->assertEqual($suggestions, array('block__system', 'block__system_menu_block', 'block__system_menu_block__menu_admin', 'block__machinename'));
   }
 
 }
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 92a2c45..0adab64 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -924,6 +924,20 @@ function field_page_build(&$page) {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function field_theme_suggestions_field_alter(array &$suggestions, array $variables) {
+  $element = $variables['element'];
+
+  $suggestions = array_merge($suggestions, array(
+    'field__' . $element['#field_type'],
+    'field__' . $element['#field_name'],
+    'field__' . $element['#bundle'],
+    'field__' . $element['#field_name'] . '__' . $element['#bundle'],
+  ));
+}
+
+/**
  * Prepares variables for field templates.
  *
  * Default template: field.html.twig.
@@ -977,14 +991,6 @@ function template_preprocess_field(&$variables, $hook) {
     $variables['attributes']['class'][] = 'clearfix';
   }
 
-  // Add specific suggestions that can override the default implementation.
-  $variables['theme_hook_suggestions'] = array(
-    'field__' . $element['#field_type'],
-    'field__' . $element['#field_name'],
-    'field__' . $element['#bundle'],
-    'field__' . $element['#field_name'] . '__' . $element['#bundle'],
-  );
-
   static $default_attributes;
   if (!isset($default_attributes)) {
     $default_attributes = new Attribute;
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 18dd4e9..0055ef8 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -960,6 +960,28 @@ function forum_preprocess_block(&$variables) {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function forum_theme_suggestions_forums_alter(array &$suggestions, array $variables) {
+  // Provide separate template suggestions based on what's being output. Topic
+  // ID is also accounted for. Check both variables to be safe then the inverse.
+  // Forums with topic IDs take precedence.
+  if ($variables['forums'] && !$variables['topics']) {
+    $suggestions[] = 'forums__containers';
+    $suggestions[] = 'forums__' . $variables['tid'];
+    $suggestions[] = 'forums__containers__' . $variables['tid'];
+  }
+  elseif (!$variables['forums'] && $variables['topics']) {
+    $suggestions[] = 'forums__topics';
+    $suggestions[] = 'forums__' . $variables['tid'];
+    $suggestions[] = 'forums__topics__' . $variables['tid'];
+  }
+  else {
+    $suggestions[] = 'forums__' . $variables['tid'];
+  }
+}
+
+/**
  * Prepares variables for forums templates.
  *
  * Default template: forums.html.twig.
@@ -1005,23 +1027,6 @@ function template_preprocess_forums(&$variables) {
     else {
       $variables['topics'] = array();
     }
-
-    // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
-    // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
-    if ($variables['forums'] && !$variables['topics']) {
-      $variables['theme_hook_suggestions'][] = 'forums__containers';
-      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
-      $variables['theme_hook_suggestions'][] = 'forums__containers__' . $variables['tid'];
-    }
-    elseif (!$variables['forums'] && $variables['topics']) {
-      $variables['theme_hook_suggestions'][] = 'forums__topics';
-      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
-      $variables['theme_hook_suggestions'][] = 'forums__topics__' . $variables['tid'];
-    }
-    else {
-      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
-    }
-
   }
   else {
     $variables['forums'] = array();
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 45f19c7..8524b55 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -667,6 +667,18 @@ function node_preprocess_block(&$variables) {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function node_theme_suggestions_node_alter(array &$suggestions, array $variables) {
+  $node = $variables['elements']['#node'];
+
+  $suggestions = array_merge($suggestions, array(
+    'node__' . $node->bundle(),
+    'node__' . $node->id(),
+  ));
+}
+
+/**
  * Prepares variables for node templates.
  *
  * Default template: node.html.twig.
@@ -755,11 +767,6 @@ function template_preprocess_node(&$variables) {
   if (isset($variables['preview'])) {
     $variables['attributes']['class'][] = 'preview';
   }
-
-  // Clean up name so there are no underscores.
-  $variables['theme_hook_suggestions'][] = 'node__' . $node->bundle();
-  $variables['theme_hook_suggestions'][] = 'node__' . $node->id();
-
   $variables['content_attributes']['class'][] = 'content';
 }
 
diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc
index a39a815..0ca1d8f 100644
--- a/core/modules/search/search.pages.inc
+++ b/core/modules/search/search.pages.inc
@@ -75,6 +75,13 @@ function search_view($module = NULL, $keys = '') {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function search_theme_suggestions_search_results_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'search_results__' . $variables['module'];
+}
+
+/**
  * Prepares variables for search results templates.
  *
  * Default template: search-results.html.twig.
@@ -101,7 +108,13 @@ function template_preprocess_search_results(&$variables) {
   // @todo Revisit where this help text is added, see also
   //   http://drupal.org/node/1918856.
   $variables['help'] = search_help('search#noresults', drupal_help_arg());
-  $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'];
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function search_theme_suggestions_search_result_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'search_result__' . $variables['module'];
 }
 
 /**
@@ -150,7 +163,6 @@ function template_preprocess_search_result(&$variables) {
   // Provide separated and grouped meta information..
   $variables['info_split'] = $info;
   $variables['info'] = implode(' - ', $info);
-  $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'];
 }
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php
new file mode 100644
index 0000000..39c2cec
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Theme\ThemeSuggestionsAlterTest.
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests theme suggestion alter hooks.
+ */
+class ThemeSuggestionsAlterTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('theme_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme suggestions alter',
+      'description' => 'Test theme suggestion alter hooks.',
+      'group' => 'Theme',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    theme_enable(array('test_theme'));
+  }
+
+  /**
+   * Tests that theme suggestion alter hooks work for templates.
+   */
+  function testTemplateSuggestionsAlter() {
+    $this->drupalGet('theme-test/suggestion-alter');
+    $this->assertText('Original template.', 'Original template rendered.');
+
+    // Enable test_theme and test that themes can alter template suggestions.
+    config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    $this->drupalGet('theme-test/suggestion-alter');
+    $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme.', 'Template suggestion provided by theme rendered.');
+
+    // Enable the theme_suggestions_test module to test modules implementing
+    // suggestions alter hooks.
+    module_enable(array('theme_suggestions_test'));
+    $this->drupalGet('theme-test/suggestion-alter');
+    $this->assertText('Template overridden based on new theme suggestion provided by a module.', 'Template suggestion provided by module rendered.');
+  }
+
+  /**
+   * Tests that theme suggestion alter hooks work for theme functions.
+   */
+  function testThemeFunctionSuggestionsAlter() {
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Original theme function.', 'Original theme function rendered.');
+
+    // Enable test_theme and test that themes can alter theme suggestions.
+    config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Theme function overridden based on new theme suggestion provided by the test_theme theme.', 'Theme function suggestion provided by theme rendered.');
+
+    // Enable the theme_suggestions_test module to test modules implementing
+    // suggestions alter hooks.
+    module_enable(array('theme_suggestions_test'));
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Theme function overridden based on new theme suggestion provided by a module.', 'Theme function suggestion provided by module rendered.');
+  }
+
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c15f95d..767b499 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1019,6 +1019,48 @@ function system_menu() {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function system_theme_suggestions_html_alter(array &$suggestions, array $variables) {
+  $suggestions = array_merge($suggestions, theme_get_suggestions(arg(), 'html'));
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function system_theme_suggestions_page_alter(array &$suggestions, array $variables) {
+  $suggestions = array_merge($suggestions, theme_get_suggestions(arg(), 'page'));
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function system_theme_suggestions_maintenance_page_alter(array &$suggestions, array $variables) {
+  // Dead databases will show error messages so supplying this template will
+  // allow themers to override the page and the content completely.
+  $offline = defined('MAINTENANCE_MODE');
+  try {
+    drupal_is_front_page();
+  }
+  catch (Exception $e) {
+    // The database is not yet available.
+    $offline = TRUE;
+  }
+  if ($offline) {
+    $suggestions[] = 'maintenance_page__offline';
+  }
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function system_theme_suggestions_region_alter(array &$suggestions, array $variables) {
+  if (!empty($variables['elements']['#region'])) {
+    $suggestions[] = 'region__' . $variables['elements']['#region'];
+  }
+}
+
+/**
  * Proxies to the plugin class' form method.
  *
  * @todo This needs more explanation, an @see or two, and parameter
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml
new file mode 100644
index 0000000..95f429f
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml
@@ -0,0 +1,7 @@
+name: 'Theme suggestions test'
+type: module
+description: 'Support module for testing theme suggestions.'
+package: Testing
+version: VERSION
+core: 8.x
+hidden: true
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
new file mode 100644
index 0000000..4b50763
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Support module for testing theme suggestions.
+ */
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'theme_test_suggestions__' . 'module_override';
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_suggestions_test_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'theme_test_function_suggestions__' . 'module_override';
+}
diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
index c790a84..4ee66ca 100644
--- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
@@ -31,4 +31,18 @@ function functionTemplateOverridden() {
     );
   }
 
+  /**
+   * Menu callback for testing suggestion alter hooks with template files.
+   */
+  function suggestionAlter() {
+    return array('#theme' => 'theme_test_suggestions');
+  }
+
+  /**
+   * Menu callback for testing suggestion alter hooks with theme functions.
+   */
+  function functionSuggestionAlter() {
+    return array('#theme' => 'theme_test_function_suggestions');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig
new file mode 100644
index 0000000..dfc848c
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Original template.
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index 51cb6d3..67f84fb 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -14,6 +14,13 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_template_test_2'] = array(
     'template' => 'theme_test.template_test',
   );
+  $items['theme_test_suggestions'] = array(
+    'template' => 'theme-test-suggestions',
+    'variables' => array(),
+  );
+  $items['theme_test_function_suggestions'] = array(
+    'variables' => array(),
+  );
   $items['theme_test_foo'] = array(
     'variables' => array('foo' => NULL),
   );
@@ -52,6 +59,14 @@ function theme_test_menu() {
     'theme callback' => '_theme_custom_theme',
     'type' => MENU_CALLBACK,
   );
+  $items['theme-test/suggestion-alter'] = array(
+    'title' => 'Test suggestion alter hook for template files',
+    'route_name' => 'suggestion_alter',
+  );
+  $items['theme-test/function-suggestion-alter'] = array(
+    'title' => 'Test suggestion alter hook for theme functions',
+    'route_name' => 'function_suggestion_alter',
+  );
   $items['theme-test/alter'] = array(
     'title' => 'Suggestion',
     'page callback' => '_theme_test_alter',
@@ -197,3 +212,10 @@ function template_preprocess_theme_test_render_element(&$variables) {
 function theme_theme_test_render_element_children($variables) {
   return drupal_render($variables['element']);
 }
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ */
+function theme_theme_test_function_suggestions($variables) {
+  return 'Original theme function.';
+}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 866171e..709d86e 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -4,3 +4,15 @@ function_template_override:
     _content: '\Drupal\theme_test\ThemeTestController::functionTemplateOverridden'
   requirements:
     _permission: 'access content'
+suggestion_alter:
+  pattern: '/theme-test/suggestion-alter'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::suggestionAlter'
+  requirements:
+    _permission: 'access content'
+function_suggestion_alter:
+  pattern: '/theme-test/function-suggestion-alter'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::functionSuggestionAlter'
+  requirements:
+    _permission: 'access content'
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig
new file mode 100644
index 0000000..26ce57b
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template overridden based on new theme suggestion provided by a module.
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig
new file mode 100644
index 0000000..dee829f
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template overridden based on new theme suggestion provided by the test_theme theme.
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 62b5abf..9b10b2b 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -29,3 +29,43 @@ function test_theme_theme_test__suggestion($variables) {
 function test_theme_theme_test_alter_alter(&$data) {
   $data = 'test_theme_theme_test_alter_alter was invoked';
 }
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  // Theme alter hooks run after module alter hooks, so add this theme
+  // suggestion to the beginning of the array so that the suggestion added by
+  // the theme_suggestions_test module can be picked up when that module is
+  // enabled.
+  array_unshift($suggestions, 'theme_test_suggestions__' . 'theme_override');
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function test_theme_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
+  // Theme alter hooks run after module alter hooks, so add this theme
+  // suggestion to the beginning of the array so that the suggestion added by
+  // the theme_suggestions_test module can be picked up when that module is
+  // enabled.
+  array_unshift($suggestions, 'theme_test_function_suggestions__' . 'theme_override');
+}
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ *
+ * Implements the theme_test_function_suggestions__theme_override suggestion.
+ */
+function test_theme_theme_test_function_suggestions__theme_override($variables) {
+  return 'Theme function overridden based on new theme suggestion provided by the test_theme theme.';
+}
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ *
+ * Implements the theme_test_function_suggestions__module_override suggestion.
+ */
+function test_theme_theme_test_function_suggestions__module_override($variables) {
+  return 'Theme function overridden based on new theme suggestion provided by a module.';
+}
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index aedc6b0..8f671de 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -158,6 +158,44 @@ function hook_preprocess_HOOK(&$variables) {
 }
 
 /**
+ * Alters theme suggestions.
+ *
+ * Provide alternative theme function or template suggestions for theme hooks.
+ *
+ * @param array $suggestions
+ *   An array of theme suggestions.
+ * @param array $variables
+ *   An array of variables passed to the theme hook. Note that this hook is
+ *   invoked before any preprocessing.
+ * @param string $hook
+ *   The theme hook being called.
+ *
+ * @see hook_theme_suggestions_HOOK_alter()
+ */
+function hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  if ($hook == 'node') {
+    $suggestions[] = 'node__' . $variables['elements']['#langcode'];
+  }
+}
+
+/**
+ * Alters theme suggestions for a specific theme hook.
+ *
+ * @param array $suggestions
+ *   An array of theme suggestions.
+ * @param array $variables
+ *   An array of variables passed to the theme hook. Note that this hook is
+ *   invoked before any preprocessing.
+ *
+ * @see hook_theme_suggestions_alter()
+ */
+function hook_theme_suggestions_HOOK_alter(array &$suggestions, array $variables) {
+  if (empty($variables['header'])) {
+    $suggestions[] = 'hookname__' . 'no_header';
+  }
+}
+
+/**
  * Process theme variables for templates.
  *
  * This hook allows modules to process theme variables for theme templates. It
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 402a8e6..f709a44 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -431,6 +431,16 @@ function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcod
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function taxonomy_theme_suggestions_taxonomy_term_alter(array &$suggestions, array $variables) {
+  $term = $variables['elements']['#term'];
+
+  $suggestions[] = 'taxonomy_term__' . $term->bundle();
+  $suggestions[] = 'taxonomy_term__' . $term->id();
+}
+
+/**
  * Prepares variables for taxonomy term templates.
  *
  * Default template: taxonomy-term.html.twig.
@@ -471,9 +481,6 @@ function template_preprocess_taxonomy_term(&$variables) {
   $variables['attributes']['class'][] = 'taxonomy-term';
   $vocabulary_name_css = str_replace('_', '-', $term->bundle());
   $variables['attributes']['class'][] = 'vocabulary-' . $vocabulary_name_css;
-
-  $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->bundle();
-  $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->id();
 }
 
 /**
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 2b0a75a..978227d 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -238,18 +238,14 @@ function views_plugin_list() {
  */
 function views_preprocess_node(&$variables) {
   Drupal::moduleHandler()->loadInclude('node', 'views.inc');
-  // The 'view' attribute of the node is added in views_preprocess_node()
+  // The 'view' attribute of the node is added in
+  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
   if (!empty($variables['node']->view) && $variables['node']->view->storage->id()) {
     $variables['view'] = $variables['node']->view;
-    $variables['theme_hook_suggestions'][] = 'node__view__' . $variables['node']->view->storage->id();
-    if (!empty($variables['node']->view->current_display)) {
-      $variables['theme_hook_suggestions'][] = 'node__view__' . $variables['node']->view->storage->id() . '__' . $variables['node']->view->current_display;
-
-      // If a node is being rendered in a view, and the view does not have a path,
-      // prevent drupal from accidentally setting the $page variable:
-      if ($variables['page'] && $variables['view_mode'] == 'full' && !$variables['view']->display_handler->hasPath()) {
-        $variables['page'] = FALSE;
-      }
+    // If a node is being rendered in a view, and the view does not have a path,
+    // prevent drupal from accidentally setting the $page variable:
+    if ($variables['page'] && $variables['view_mode'] == 'full' && !$variables['view']->display_handler->hasPath()) {
+      $variables['page'] = FALSE;
     }
   }
 
@@ -260,16 +256,39 @@ function views_preprocess_node(&$variables) {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function views_theme_suggestions_node_alter(array &$suggestions, array $variables) {
+  $node = $variables['elements']['#node'];
+  if (!empty($node->view) && $node->view->storage->id()) {
+    $suggestions[] = 'node__view__' . $node->view->storage->id();
+    if (!empty($node->view->current_display)) {
+      $suggestions[] = 'node__view__' . $node->view->storage->id() . '__' . $node->view->current_display;
+    }
+  }
+}
+
+/**
  * A theme preprocess function to automatically allow view-based node
  * templates if called from a view.
  */
 function views_preprocess_comment(&$variables) {
-  // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
+  // The view data is added to the comment in
+  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
   if (!empty($variables['comment']->view) && $variables['comment']->view->storage->id()) {
     $variables['view'] = &$variables['comment']->view;
-    $variables['theme_hook_suggestions'][] = 'comment__view__' . $variables['comment']->view->storage->id();
-    if (!empty($variables['node']->view->current_display)) {
-      $variables['theme_hook_suggestions'][] = 'comment__view__' . $variables['comment']->view->storage->id() . '__' . $variables['comment']->view->current_display;
+  }
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function views_theme_suggestions_comment_alter(array &$suggestions, array $variables) {
+  $comment = $variables['elements']['#comment'];
+  if (!empty($comment->view) && $comment->view->storage->id()) {
+    $suggestions[] = 'comment__view__' . $comment->view->storage->id();
+    if (!empty($comment->view->current_display)) {
+      $suggestions[] = 'comment__view__' . $comment->view->storage->id() . '__' . $comment->view->current_display;
     }
   }
 }
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index d998763..60c198d 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -485,5 +485,11 @@ function template_preprocess_views_ui_view_preview_section(&$variables) {
     );
     $variables['links'] = $build;
   }
-  $variables['theme_hook_suggestions'][] = 'views_ui_view_preview_section__' . $variables['section'];
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function views_ui_theme_suggestions_views_ui_view_preview_section_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'views_ui_view_preview_section__' . $variables['section'];
 }
diff --git a/core/themes/engines/phptemplate/phptemplate.engine b/core/themes/engines/phptemplate/phptemplate.engine
index 718d460..f451dff 100644
--- a/core/themes/engines/phptemplate/phptemplate.engine
+++ b/core/themes/engines/phptemplate/phptemplate.engine
@@ -42,7 +42,7 @@ function phptemplate_extension() {
  * @return
  *   The output generated by the template.
  */
-function phptemplate_render_template($template_file, $variables) {
+function phptemplate_render_template($template_file, $variables, $suggestions) {
   // Extract the variables to a local namespace
   extract($variables, EXTR_SKIP);
 
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index b541cdc..838aef0 100644
--- a/core/themes/engines/twig/twig.engine
+++ b/core/themes/engines/twig/twig.engine
@@ -46,7 +46,7 @@ function twig_init($template) {
  * @return
  *   The output generated by the template, plus any debug information.
  */
-function twig_render_template($template_file, $variables) {
+function twig_render_template($template_file, $variables, $suggestions) {
   $variables['_references'] = array();
   $output = array(
     'debug_prefix'    => '',
@@ -57,10 +57,9 @@ function twig_render_template($template_file, $variables) {
   if (settings()->get('twig_debug', FALSE)) {
     $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
     $output['debug_prefix'] .= "\n<!-- CALL: theme('{$variables['theme_hook_original']}') -->";
-    if (!empty($variables['theme_hook_suggestions'])) {
+    if (!empty($suggestions)) {
       $extension = twig_extension();
       $current_template = basename($template_file);
-      $suggestions = $variables['theme_hook_suggestions'];
       $suggestions[] = $variables['theme_hook_original'];
       foreach ($suggestions as $key => &$suggestion) {
         $template = strtr($suggestion, '_', '-') . $extension;
