diff --git a/core/lib/Drupal/Core/Extension/Extension.php b/core/lib/Drupal/Core/Extension/Extension.php
index 17ca3fe7dc3d6fabc5aedec246aa4b4a5cf0653d..62977ba7ce5c786ad8045768c956c87164fcc4e9 100644
--- a/core/lib/Drupal/Core/Extension/Extension.php
+++ b/core/lib/Drupal/Core/Extension/Extension.php
@@ -209,4 +209,17 @@ public function isExperimental(): bool {
         && $this->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL);
   }
 
+  /**
+   * Checks if an extension is marked as obsolete.
+   *
+   * @return bool
+   *   TRUE if an extension is marked as obsolete, FALSE otherwise.
+   */
+  public function isObsolete(): bool {
+    // This function checks for 'lifecycle: obsolete' to determine if an
+    // extension is marked as obsolete.
+    return (isset($this->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])
+        && $this->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE);
+  }
+
 }
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index 4d30102d06bb791a74fbc2fc309bcc3f862ba388..39c67dac7979b96be4830a49be0f0b01ce9f1a56 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -195,7 +195,7 @@ public function systemAdminMenuBlockPage() {
   }
 
   /**
-   * Returns a theme listing.
+   * Returns a theme listing which excludes obsolete themes.
    *
    * @return string
    *   An HTML string of the theme listing page.
@@ -206,6 +206,11 @@ public function themesPage() {
     $config = $this->config('system.theme');
     // Get all available themes.
     $themes = $this->themeHandler->rebuildThemeData();
+
+    // Remove obsolete themes.
+    $themes = array_filter($themes, function ($theme) {
+      return !$theme->isObsolete();
+    });
     uasort($themes, [ThemeExtensionList::class, 'sortByName']);
 
     $theme_default = $config->get('default');
diff --git a/core/modules/system/tests/src/Functional/System/ThemeTest.php b/core/modules/system/tests/src/Functional/System/ThemeTest.php
index 7765a2b93e60436953a43195bfa0cfe0b682e498..e74bf68b23cefd7da2ea9053bc7dec2116ab77b8 100644
--- a/core/modules/system/tests/src/Functional/System/ThemeTest.php
+++ b/core/modules/system/tests/src/Functional/System/ThemeTest.php
@@ -320,6 +320,10 @@ public function testAdministrationTheme() {
     $this->drupalGet('admin/appearance');
     $this->submitForm($edit, 'Save configuration');
 
+    // Check that obsolete themes are not displayed.
+    $this->drupalGet('admin/appearance');
+    $this->assertSession()->pageTextNotContains('Obsolete test theme');
+
     // Check that the administration theme is used on an administration page.
     $this->drupalGet('admin/config');
     $this->assertSession()->responseContains('core/themes/seven');
diff --git a/core/modules/system/tests/themes/obsolete_theme_test/obsolete_theme_test.info.yml b/core/modules/system/tests/themes/obsolete_theme_test/obsolete_theme_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..46f10e3b05cedd970a3077f0eea4ccffa00ea4cd
--- /dev/null
+++ b/core/modules/system/tests/themes/obsolete_theme_test/obsolete_theme_test.info.yml
@@ -0,0 +1,7 @@
+name: 'Obsolete theme test'
+type: theme
+description: 'Obsolete test theme.'
+version: VERSION
+lifecycle: obsolete
+lifecycle_link: 'https://example.com/obsolete'
+base theme: false