diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 001650a3c9f9a3cee265b2a59b971bc63649a0cb..1d0fc29f5fc15347d144eb9c421c40d53a2ad55a 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -177,6 +177,11 @@ theme_settings:
         use_default:
           type: boolean
           label: 'Use default'
+    third_party_settings:
+      type: sequence
+      label: 'Third party settings'
+      sequence:
+        - type: theme_settings.third_party.[%key]
 
 theme_breakpoints_default:
   type: sequence
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 34dbdc2a93997581d0cb40f49cbe773fda108d9d..7bbd4ff90c9b1dca6276ec17c54abdc4ce063fc5 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -323,9 +323,6 @@ function drupal_find_theme_templates($cache, $extension, $path) {
  *
  * The final setting is obtained from the last value found in the following
  * sources:
- * - the default theme-specific settings defined in any base theme's .info.yml
- *   file
- * - the default theme-specific settings defined in the theme's .info.yml file
  * - the saved values from the global theme settings form
  * - the saved values from the theme's settings form
  * To only retrieve the default global theme setting, an empty string should be
@@ -340,6 +337,7 @@ function drupal_find_theme_templates($cache, $extension, $path) {
  *   The value of the requested setting, NULL if the setting does not exist.
  */
 function theme_get_setting($setting_name, $theme = NULL) {
+  /** @var \Drupal\Core\Theme\ThemeSettings[] $cache */
   $cache = &drupal_static(__FUNCTION__, array());
 
   // If no key is given, use the current theme if we can determine it.
@@ -350,33 +348,13 @@ function theme_get_setting($setting_name, $theme = NULL) {
   if (empty($cache[$theme])) {
     // Create a theme settings object.
     $cache[$theme] = new ThemeSettings($theme);
+    // Get the global settings from configuration.
+    $cache[$theme]->setData(\Drupal::config('system.theme.global')->get());
 
-    // Get the values for the theme-specific settings from the .info.yml files
-    // of the theme and all its base themes.
-    $themes = list_themes();
-    if ($theme && isset($themes[$theme])) {
+    $themes = \Drupal::service('theme_handler')->listInfo();
+    if (isset($themes[$theme])) {
       $theme_object = $themes[$theme];
 
-      // Create a list which includes the current theme and all its base themes.
-      if (isset($theme_object->base_themes)) {
-        $theme_keys = array_keys($theme_object->base_themes);
-        $theme_keys[] = $theme;
-      }
-      else {
-        $theme_keys = array($theme);
-      }
-      // Read hard-coded default settings from the theme info files.
-      foreach ($theme_keys as $theme_key) {
-        if (!empty($themes[$theme_key]->info['settings'])) {
-          $cache[$theme]->merge($themes[$theme_key]->info['settings']);
-        }
-      }
-    }
-
-    // Get the global settings from configuration.
-    $cache[$theme]->merge(\Drupal::config('system.theme.global')->get());
-
-    if ($theme && isset($themes[$theme])) {
       // Retrieve configured theme-specific settings, if any.
       try {
         if ($theme_settings = \Drupal::config($theme . '.settings')->get()) {
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index e99397e34163dfed5fdfe560554cf32da3169d85..72ae5543f361bc85067866df4a6945db4727061f 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -90,10 +90,9 @@ public function __construct(ConfigFactoryInterface $config_factory, StorageInter
    */
   public function installDefaultConfig($type, $name) {
     $extension_path = drupal_get_path($type, $name);
-    // If the extension provides configuration schema clear the definitions.
-    if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY)) {
-      // Refresh the schema cache if installing default configuration and the
-      // extension has a configuration schema directory.
+    // Refresh the schema cache if the extension provides configuration schema
+    // or is a theme.
+    if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') {
       $this->typedConfig->clearCachedDefinitions();
     }
 
diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php
index 4d448cd5878d86380551bab7f15e90468e6a6500..b868d3597c1faf4cab4829685ea759c80bfeb223 100644
--- a/core/modules/config/src/Tests/ConfigImportUITest.php
+++ b/core/modules/config/src/Tests/ConfigImportUITest.php
@@ -84,7 +84,6 @@ function testImport() {
     $system_theme = \Drupal::config('system.theme')->get();
     $system_theme['default'] = 'bartik';
     $staging->write('system.theme', $system_theme);
-    $staging->write('bartik.settings', $install_storage->read('bartik.settings'));
 
     // Read the action config from module default config folder.
     $action_settings = $install_storage->read('action.settings');
@@ -108,7 +107,6 @@ function testImport() {
     $this->assertText('core.extension');
     $this->assertText('system.theme');
     $this->assertText('action.settings');
-    $this->assertText('bartik.settings');
     $this->assertFieldById('edit-submit', t('Import all'));
 
     // Import and verify that both do not appear anymore.
@@ -118,7 +116,6 @@ function testImport() {
     $this->assertNoText('core.extension');
     $this->assertNoText('system.theme');
     $this->assertNoText('action.settings');
-    $this->assertNoText('bartik.settings');
 
     $this->assertNoFieldById('edit-submit', t('Import all'));
 
diff --git a/core/modules/config/src/Tests/DefaultConfigTest.php b/core/modules/config/src/Tests/DefaultConfigTest.php
index 48805612e7faffe3e630afd6b8c3b89555badcff..99ff07a6d5159affe012ed8d5ecc4c6e1e50dab6 100644
--- a/core/modules/config/src/Tests/DefaultConfigTest.php
+++ b/core/modules/config/src/Tests/DefaultConfigTest.php
@@ -10,7 +10,9 @@
 use Drupal\config_test\TestInstallStorage;
 use Drupal\Core\Config\InstallStorage;
 use Drupal\Core\Config\TypedConfigManager;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\simpletest\KernelTestBase;
+use Symfony\Component\DependencyInjection\Reference;
 
 /**
  * Tests that default configuration provided by all modules matches schema.
@@ -24,23 +26,46 @@ class DefaultConfigTest extends KernelTestBase {
   /**
    * Modules to enable.
    *
+   * Enable the system module so that system_config_schema_info_alter() fires.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'config_test');
+
+  /**
+   * Themes which provide default configuration and need enabling.
+   *
+   * If a theme provides default configuration but does not have a schema
+   * because it can rely on schemas added by system_config_schema_info_alter()
+   * then this test needs to enable it.
+   *
    * @var array
    */
-  public static $modules = array('config_test');
+  protected $themes = ['seven'];
+
+  protected function setUp() {
+    parent::setUp();
+    \Drupal::service('theme_handler')->install($this->themes);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function containerBuild(ContainerBuilder $container) {
+    parent::containerBuild($container);
+    $container->register('DefaultConfigTest.schema_storage')
+      ->setClass('\Drupal\config_test\TestInstallStorage')
+      ->addArgument(InstallStorage::CONFIG_SCHEMA_DIRECTORY);
+
+    $definition = $container->getDefinition('config.typed');
+    $definition->replaceArgument(1, new Reference('DefaultConfigTest.schema_storage'));
+  }
 
   /**
    * Tests default configuration data type.
    */
   public function testDefaultConfig() {
-    // Create a typed config manager with access to configuration schema in
-    // every module, profile and theme.
-    $typed_config = new TypedConfigManager(
-      \Drupal::service('config.storage'),
-      new TestInstallStorage(InstallStorage::CONFIG_SCHEMA_DIRECTORY),
-      \Drupal::service('cache.discovery'),
-      \Drupal::service('module_handler')
-    );
-
+    $typed_config = \Drupal::service('config.typed');
     // Create a configuration storage with access to default configuration in
     // every module, profile and theme.
     $default_config_storage = new TestInstallStorage();
diff --git a/core/modules/shortcut/config/schema/shortcut.schema.yml b/core/modules/shortcut/config/schema/shortcut.schema.yml
index 93326994ad2b126dca9e7a7bf5323e6d5e9212e2..7d9420ba12127336ecd1415759ed2a21838f8dca 100644
--- a/core/modules/shortcut/config/schema/shortcut.schema.yml
+++ b/core/modules/shortcut/config/schema/shortcut.schema.yml
@@ -10,3 +10,12 @@ shortcut.set.*:
     label:
       type: label
       label: 'Label'
+
+# Schema for theme settings.
+theme_settings.third_party.shortcut:
+  type: mapping
+  label: 'Shortcut settings'
+  mapping:
+    module_link:
+      type: boolean
+      label: 'Add shortcut link'
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 601a149b940de5e6f6ffb56e157202af6834ecf3..db9888263daa00547b68d13992d4f0b43a48b1a3 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -330,7 +330,7 @@ function shortcut_preprocess_page(&$variables) {
       $route_parameters = array('shortcut' => $shortcut_id);
     }
 
-    if (theme_get_setting('shortcut_module_link')) {
+    if (theme_get_setting('third_party_settings.shortcut.module_link')) {
       $variables['title_suffix']['add_or_remove_shortcut'] = array(
         '#attached' => array(
           'library' => array(
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index dfe249c8ee4ffdbf20ab480f0c01b2b06016a7bc..7f3587fa371e1efb693112bee4055f3e6da7c0c9 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -218,17 +218,6 @@ function testListThemes() {
     $this->assertIdentical($themes['test_subtheme']->prefix, 'twig', 'Subtheme\'s object includes the theme engine prefix.');
   }
 
-  /**
-   * Test the theme_get_setting() function.
-   */
-  function testThemeGetSetting() {
-    $this->container->get('theme_handler')->install(array('test_subtheme'));
-    \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('test_theme'));
-    $this->assertIdentical(theme_get_setting('theme_test_setting'), 'default value', 'theme_get_setting() uses the default theme automatically.');
-    $this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.');
-    $this->assertIdentical(theme_get_setting('basetheme_only', 'test_subtheme'), 'base theme value', 'Base theme\'s default settings values are inherited by subtheme.');
-  }
-
   /**
    * Tests child element rendering for 'render element' theme hooks.
    */
diff --git a/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml b/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml
index 6a95a1c92d5fc726c02f39ebde06532c41d7b28b..1c07b3f022110a03d7d462b9b0d45e312e1bdaa4 100644
--- a/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml
+++ b/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml
@@ -11,6 +11,3 @@ stylesheets-override:
 stylesheets-remove:
   - base-remove.css
   - base-remove.sub-override.css
-settings:
-  basetheme_only: 'base theme value'
-  subtheme_override: 'base theme value'
diff --git a/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml b/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml
index 921f2f04149bcb15dbf9cf1c14638fc0742c5a21..b15efa95ddc104d5ef3cdbe209ec4182a1ef646c 100644
--- a/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml
+++ b/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml
@@ -14,5 +14,3 @@ stylesheets-remove:
   - sub-remove.css
   - base-add.sub-remove.css
   - base-override.sub-remove.css
-settings:
-  subtheme_override: 'subtheme value'
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.info.yml b/core/modules/system/tests/themes/test_theme/test_theme.info.yml
index fd5aae5c5e34bbfb730fe4a798a9154cf935e07b..9eebc9036efdf1a07636f42e2220660d1338c6b6 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.info.yml
+++ b/core/modules/system/tests/themes/test_theme/test_theme.info.yml
@@ -16,8 +16,6 @@ base theme: classy
 core: 8.x
 stylesheets-remove:
   - system.module.css
-settings:
-  theme_test_setting: 'default value'
 regions:
   content: Content
   left: Left
diff --git a/core/themes/bartik/bartik.info.yml b/core/themes/bartik/bartik.info.yml
index 36d9c25880385c5c7041548f05661530ad8443fb..cb1bcb1c01cd8f919b771be919eb29172276b9eb 100644
--- a/core/themes/bartik/bartik.info.yml
+++ b/core/themes/bartik/bartik.info.yml
@@ -29,6 +29,3 @@ regions:
   footer_thirdcolumn: 'Footer third column'
   footer_fourthcolumn: 'Footer fourth column'
   footer: Footer
-
-settings:
-  shortcut_module_link: false
diff --git a/core/themes/bartik/config/install/bartik.settings.yml b/core/themes/bartik/config/install/bartik.settings.yml
deleted file mode 100644
index 48877a3811a584cdf7d773767e4957bba07252e1..0000000000000000000000000000000000000000
--- a/core/themes/bartik/config/install/bartik.settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-# @todo There is no UI yet for configuring this, but the setting is included
-#   here, because ConfigImportUITest requires a non-empty bartik.settings.yml
-#   file: https://drupal.org/node/2235901.
-shortcut_module_link: false
diff --git a/core/themes/bartik/config/schema/bartik.schema.yml b/core/themes/bartik/config/schema/bartik.schema.yml
index bebcfc17a9ba30cefa234c04bdeea64a83bb22c5..ef355e3f3a9a4060f39ed2965d65167633e1ac7d 100644
--- a/core/themes/bartik/config/schema/bartik.schema.yml
+++ b/core/themes/bartik/config/schema/bartik.schema.yml
@@ -3,9 +3,3 @@
 bartik.settings:
   type: theme_settings
   label: 'Bartik settings'
-  mapping:
-    # @todo Module-specific settings should be defined by the module:
-    #   https://drupal.org/node/2235901.
-    shortcut_module_link:
-      type: boolean
-      label: 'Shortcut module link'
diff --git a/core/themes/seven/config/install/seven.settings.yml b/core/themes/seven/config/install/seven.settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6bba58bc7983bf66a3a0fcd247ecdc6fded269c4
--- /dev/null
+++ b/core/themes/seven/config/install/seven.settings.yml
@@ -0,0 +1,3 @@
+third_party_settings:
+  shortcut:
+    module_link: true
diff --git a/core/themes/seven/config/schema/seven.schema.yml b/core/themes/seven/config/schema/seven.schema.yml
index 6d4c86e241630e5bfe78c7926a9bae839bae9c2b..8e40d45b75fc7ac1873613c82b379ec0e54c085f 100644
--- a/core/themes/seven/config/schema/seven.schema.yml
+++ b/core/themes/seven/config/schema/seven.schema.yml
@@ -3,7 +3,3 @@
 seven.settings:
   type: theme_settings
   label: 'Seven settings'
-
-seven.breakpoints:
-  type: theme_breakpoints_default
-  label: 'Seven breakpoints'
diff --git a/core/themes/seven/seven.info.yml b/core/themes/seven/seven.info.yml
index 8616b6dbc979f3505485137f6ccd0c0eee698089..fdf916e46ebfcbe3739e62c7f7e629edb34907fb 100644
--- a/core/themes/seven/seven.info.yml
+++ b/core/themes/seven/seven.info.yml
@@ -25,6 +25,3 @@ regions:
   sidebar_first: 'First sidebar'
 regions_hidden:
   - sidebar_first
-
-settings:
-  shortcut_module_link: true