From 58d598d17d6b33758a344fc8b43e9cfb4f5b531a Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andy@pixelde.su>
Date: Mon, 9 Sep 2024 21:12:59 +0200
Subject: [PATCH] [FEATURE] Add color scheme switching

This patch adds the ability to switch the active color scheme
using multiple different methods in the Backend User Interface.
The two main methods are a selection of buttons in the user
dropdown at the top right of the Backend, the other one being
a setting present in User Settings.

Additionally, if the "auto" setting (default) is selected, the backend
is automatically using the systems preferred color scheme.

Considering the dark color scheme is still regarded as experimental,
the switching capabilities are currently disabled using a UserTS setting
'setup.fields.colorScheme.disabled'.

Resolves: #104868
Releases: main
Change-Id: I6704fba0930ac579582f1d21dd11368b3509328a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/85935
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benjamin Kott <benjamin.kott@outlook.com>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Andreas Nedbal <andy@pixelde.su>
Reviewed-by: Andreas Nedbal <andy@pixelde.su>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benjamin Kott <benjamin.kott@outlook.com>
---
 Build/Sources/Sass/component/_root.scss       | 24 +++++
 .../backend/color-scheme-manager.ts           | 39 ++++++++
 .../TypeScript/backend/color-scheme-switch.ts | 92 +++++++++++++++++++
 .../backend/Classes/Backend/ColorScheme.php   | 52 +++++++++++
 .../Backend/ToolbarItems/UserToolbarItem.php  | 35 +++++++
 .../Classes/Controller/BackendController.php  |  3 +
 .../Controller/ColorSchemeController.php      | 50 ++++++++++
 .../Configuration/Backend/AjaxRoutes.php      |  5 +
 .../Resources/Private/Language/locallang.xlf  | 12 +++
 .../ToolbarItems/UserToolbarItemDropDown.html |  8 +-
 .../backend/Resources/Public/Css/backend.css  |  2 +
 .../Public/JavaScript/color-scheme-manager.js | 13 +++
 .../Public/JavaScript/color-scheme-switch.js  | 38 ++++++++
 .../sysext/core/Classes/Page/PageRenderer.php |  8 ++
 ...eature-104868-AddColorSchemeSwitching.rst  | 25 +++++
 .../Classes/Controller/AbstractController.php |  1 +
 .../Controller/BackendModuleController.php    |  2 +-
 .../Private/Templates/Layout/Init.html        |  2 +-
 .../sysext/setup/Configuration/user.tsconfig  |  1 +
 typo3/sysext/setup/ext_tables.php             |  8 +-
 20 files changed, 416 insertions(+), 4 deletions(-)
 create mode 100644 Build/Sources/TypeScript/backend/color-scheme-manager.ts
 create mode 100644 Build/Sources/TypeScript/backend/color-scheme-switch.ts
 create mode 100644 typo3/sysext/backend/Classes/Backend/ColorScheme.php
 create mode 100644 typo3/sysext/backend/Classes/Controller/ColorSchemeController.php
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-manager.js
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-switch.js
 create mode 100644 typo3/sysext/core/Documentation/Changelog/13.3/Feature-104868-AddColorSchemeSwitching.rst 
 create mode 100644 typo3/sysext/setup/Configuration/user.tsconfig

diff --git a/Build/Sources/Sass/component/_root.scss b/Build/Sources/Sass/component/_root.scss
index 0cb995c69b5d..a9b43e255a2c 100644
--- a/Build/Sources/Sass/component/_root.scss
+++ b/Build/Sources/Sass/component/_root.scss
@@ -313,10 +313,34 @@
 
 [data-color-scheme="dark"] {
     color-scheme: only dark;
+
+    & .nodes-container,
+    & .tree-toolbar,
+    & .dropdown-menu,
+    & .scaffold-content-navigation-drag,
+    & .scaffold-content-navigation-switcher,
+    & .alwan,
+    & .context-menu,
+    & .flatpickr-calendar,
+    & .dragging-tooltip {
+        color-scheme: only dark;
+    }
 }
 
 [data-color-scheme="light"] {
     color-scheme: only light;
+
+    & .nodes-container,
+    & .tree-toolbar,
+    & .dropdown-menu,
+    & .scaffold-content-navigation-drag,
+    & .scaffold-content-navigation-switcher,
+    & .alwan,
+    & .context-menu,
+    & .flatpickr-calendar,
+    & .dragging-tooltip {
+        color-scheme: only light;
+    }
 }
 
 //
diff --git a/Build/Sources/TypeScript/backend/color-scheme-manager.ts b/Build/Sources/TypeScript/backend/color-scheme-manager.ts
new file mode 100644
index 000000000000..13c0000673e1
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/color-scheme-manager.ts
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read theÍ
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+enum Identifier {
+  switch = 'typo3-backend-color-scheme-switch',
+}
+
+class ColorSchemeManager {
+  constructor() {
+    document.addEventListener('typo3:color-scheme:update', this.onBroadcastSchemeUpdate.bind(this));
+  }
+
+  private onBroadcastSchemeUpdate(event: CustomEvent<{ name: string; payload: { name: string }}>) {
+    // The tab-local broadcast message is missing the "payload" object #93270
+    const colorScheme = event.detail.payload?.name || event.detail.name;
+
+    document.documentElement.setAttribute('data-color-scheme', colorScheme);
+    document.list_frame.document.documentElement.setAttribute('data-color-scheme', colorScheme);
+
+    this.updateActiveScheme(colorScheme);
+  }
+
+  private updateActiveScheme(colorScheme: string) {
+    const colorSchemeSwitch = document.querySelector(Identifier.switch);
+    colorSchemeSwitch.activeColorScheme = colorScheme;
+  }
+}
+
+export default new ColorSchemeManager();
diff --git a/Build/Sources/TypeScript/backend/color-scheme-switch.ts b/Build/Sources/TypeScript/backend/color-scheme-switch.ts
new file mode 100644
index 000000000000..52fe9d1ab3a8
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/color-scheme-switch.ts
@@ -0,0 +1,92 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read theÍ
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import { html, LitElement, TemplateResult } from 'lit';
+import { customElement, property } from 'lit/decorators';
+import AjaxRequest from '@typo3/core/ajax/ajax-request';
+import { BroadcastMessage } from '@typo3/backend/broadcast-message';
+import BroadcastService from '@typo3/backend/broadcast-service';
+import '@typo3/backend/element/icon-element';
+
+interface ColorScheme {
+  label: string,
+  icon: string,
+  value: string,
+}
+
+@customElement('typo3-backend-color-scheme-switch')
+export class ColorSchemeSwitchElement extends LitElement {
+  @property({ type: String }) activeColorScheme: string = null;
+  @property({ type: Array }) data: ColorScheme[] = null;
+
+  protected createRenderRoot(): HTMLElement | ShadowRoot {
+    return this;
+  }
+
+  protected render(): TemplateResult | symbol {
+    return html`
+      <ul class="dropdown-list">
+        ${this.data.map(item => this.renderItem(item))}
+      </ul>
+    `;
+  }
+
+  protected renderItem(colorScheme: ColorScheme): TemplateResult | symbol {
+    return html`
+      <li>
+        <button class="dropdown-item" @click="${() => this.handleClick(colorScheme.value)}" aria-current="${this.activeColorScheme === colorScheme.value ? 'true' : 'false'}">
+          <span class="dropdown-item-columns">
+            ${this.activeColorScheme === colorScheme.value ? html`
+              <span class="text-primary">
+                <typo3-backend-icon identifier="actions-dot" size="small"></typo3-backend-icon>
+              </span>
+            ` : html`
+              <typo3-backend-icon identifier="empty-empty" size="small"></typo3-backend-icon>
+            `}
+            <span class="dropdown-item-column dropdown-item-column-icon" aria-hidden="true">
+              <typo3-backend-icon identifier="${colorScheme.icon}" size="small"></typo3-backend-icon>
+            </span>
+            <span class="dropdown-item-column dropdown-item-column-title">
+              ${colorScheme.label}
+            </span>
+            <slot></slot>
+          </span>
+        </button>
+      </li>
+    `;
+  }
+
+  private async handleClick(value: string): Promise<void> {
+    this.broadcastSchemeUpdate(value);
+    await this.persistSchemeUpdate(value);
+  }
+
+  private async persistSchemeUpdate(colorScheme: string) {
+    const url = new URL(TYPO3.settings.ajaxUrls.color_scheme_update, window.location.origin);
+
+    return await (new AjaxRequest(url)).post({ colorScheme: colorScheme });
+  }
+
+  private broadcastSchemeUpdate(colorScheme: string): void {
+    const broadcastMessage = new BroadcastMessage('color-scheme', 'update', { name: colorScheme });
+
+    BroadcastService.post(broadcastMessage);
+    document.dispatchEvent(broadcastMessage.createCustomEvent('typo3'));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'typo3-backend-color-scheme-switch': ColorSchemeSwitchElement;
+  }
+}
diff --git a/typo3/sysext/backend/Classes/Backend/ColorScheme.php b/typo3/sysext/backend/Classes/Backend/ColorScheme.php
new file mode 100644
index 000000000000..c8615e14e0fc
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Backend/ColorScheme.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Backend\Backend;
+
+enum ColorScheme: string
+{
+    case auto = 'auto';
+    case light = 'light';
+    case dark = 'dark';
+
+    public function getLabel(): string
+    {
+        return match ($this) {
+            self::auto => 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:colorScheme.auto',
+            self::light => 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:colorScheme.light',
+            self::dark => 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:colorScheme.dark',
+        };
+    }
+
+    public function getIcon(): string
+    {
+        return match ($this) {
+            self::auto => 'actions-circle-half',
+            self::light => 'actions-brightness-high',
+            self::dark => 'actions-moon',
+        };
+    }
+
+    public static function getAvailableItemsForSelection(): array
+    {
+        return [
+            self::auto->value => self::auto->getLabel(),
+            self::light->value => self::light->getLabel(),
+            self::dark->value => self::dark->getLabel(),
+        ];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php b/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
index 547ce50442bc..381b5af080d5 100644
--- a/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
+++ b/typo3/sysext/backend/Classes/Backend/ToolbarItems/UserToolbarItem.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
 
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Backend\ColorScheme;
 use TYPO3\CMS\Backend\Module\ModuleProvider;
 use TYPO3\CMS\Backend\Toolbar\RequestAwareToolbarItemInterface;
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
@@ -25,6 +26,7 @@ use TYPO3\CMS\Backend\View\BackendViewFactory;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -115,6 +117,9 @@ class UserToolbarItem implements ToolbarItemInterface, RequestAwareToolbarItemIn
             'modules' => $modules,
             'switchUserMode' => $this->getBackendUser()->getOriginalUserIdWhenInSwitchUserMode() !== null,
             'recentUsers' => $mostRecentUsers,
+            'colorSchemeSwitchEnabled' => $this->getColorSchemeSwitchEnabled(),
+            'activeColorScheme' => $backendUser->uc['colorScheme'] ?? 'auto',
+            'colorSchemes' => $this->getColorSchemes(),
         ]);
         return $view->render('ToolbarItems/UserToolbarItemDropDown');
     }
@@ -153,4 +158,34 @@ class UserToolbarItem implements ToolbarItemInterface, RequestAwareToolbarItemIn
     {
         return $GLOBALS['BE_USER'];
     }
+
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    protected function getColorSchemeSwitchEnabled(): bool
+    {
+        $backendUser = $this->getBackendUser();
+        $userTS = $backendUser->getTSConfig();
+
+        return !isset($userTS['setup.']['fields.']['colorScheme.']['disabled']) || $userTS['setup.']['fields.']['colorScheme.']['disabled'] !== '1';
+    }
+
+    protected function getColorSchemes(): array
+    {
+        $schemes = [];
+
+        foreach (ColorScheme::cases() as $scheme) {
+            $schemeItem = [
+                'label' => $this->getLanguageService()->sL($scheme->getLabel()),
+                'value' => $scheme->value,
+                'icon' => $scheme->getIcon(),
+            ];
+
+            $schemes[] = $schemeItem;
+        }
+
+        return $schemes;
+    }
 }
diff --git a/typo3/sysext/backend/Classes/Controller/BackendController.php b/typo3/sysext/backend/Classes/Controller/BackendController.php
index 40cba5a7c672..0eed75282d5f 100644
--- a/typo3/sysext/backend/Classes/Controller/BackendController.php
+++ b/typo3/sysext/backend/Classes/Controller/BackendController.php
@@ -114,6 +114,9 @@ class BackendController
         $javaScriptRenderer->addJavaScriptModuleInstruction(
             JavaScriptModuleInstruction::create('@typo3/backend/hotkeys.js')
         );
+        $javaScriptRenderer->addJavaScriptModuleInstruction(
+            JavaScriptModuleInstruction::create('@typo3/backend/color-scheme-manager.js')
+        );
         // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
         $javaScriptRenderer->addJavaScriptModuleInstruction(
             JavaScriptModuleInstruction::create('@typo3/backend/storage/persistent.js')
diff --git a/typo3/sysext/backend/Classes/Controller/ColorSchemeController.php b/typo3/sysext/backend/Classes/Controller/ColorSchemeController.php
new file mode 100644
index 000000000000..90423d457841
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Controller/ColorSchemeController.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Backend\Controller;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Attribute\AsController;
+use TYPO3\CMS\Backend\Backend\ColorScheme;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Http\Response;
+
+#[AsController]
+class ColorSchemeController
+{
+    public function updateAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $colorScheme = $request->getParsedBody()['colorScheme'];
+
+        if ($request->getMethod() !== 'POST' || !ColorScheme::tryFrom($colorScheme)) {
+            return new JsonResponse(null, 400);
+        }
+
+        $backendUser = $this->getBackendUser();
+        $backendUser->uc['colorScheme'] = $colorScheme;
+        $backendUser->writeUC();
+
+        return new Response(null);
+    }
+
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}
diff --git a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
index 9f48f395ced4..de98e6986772 100644
--- a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
+++ b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
@@ -413,4 +413,9 @@ return [
         'path' => '/code-editor/codecompletion/load-templates',
         'target' => \TYPO3\CMS\Backend\Controller\CodeEditor\CodeCompletionController::class . '::loadCompletions',
     ],
+
+    'color_scheme_update' => [
+        'path' => '/color-scheme/update',
+        'target' => Controller\ColorSchemeController::class . '::updateAction',
+    ],
 ];
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang.xlf
index 8a209e9b9e9a..3b81975aa71d 100644
--- a/typo3/sysext/backend/Resources/Private/Language/locallang.xlf
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang.xlf
@@ -189,6 +189,18 @@
 			<trans-unit id="editdocument.moduleMenu.dropdown.label" resname="editdocument.moduleMenu.dropdown.label">
 				<source>Record language</source>
 			</trans-unit>
+			<trans-unit id="colorScheme" resname="colorScheme">
+				<source>Color scheme</source>
+			</trans-unit>
+			<trans-unit id="colorScheme.auto" resname="colorScheme.auto">
+				<source>OS default</source>
+			</trans-unit>
+			<trans-unit id="colorScheme.light" resname="colorScheme.light">
+				<source>Light</source>
+			</trans-unit>
+			<trans-unit id="colorScheme.dark" resname="colorScheme.dark">
+				<source>Dark</source>
+			</trans-unit>
 		</body>
 	</file>
 </xliff>
diff --git a/typo3/sysext/backend/Resources/Private/Templates/ToolbarItems/UserToolbarItemDropDown.html b/typo3/sysext/backend/Resources/Private/Templates/ToolbarItems/UserToolbarItemDropDown.html
index 7b68f971897d..806de8f69e5d 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/ToolbarItems/UserToolbarItemDropDown.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/ToolbarItems/UserToolbarItemDropDown.html
@@ -5,7 +5,8 @@
 >
 <f:be.pageRenderer
     includeJavaScriptModules="{
-        0: '@typo3/backend/switch-user.js'
+        0: '@typo3/backend/switch-user.js',
+        1: '@typo3/backend/color-scheme-switch.js',
     }"
 />
 <p class="h3 dropdown-headline">{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.user')}</p>
@@ -58,6 +59,11 @@
         </f:for>
     </ul>
 </f:if>
+<f:if condition="{colorSchemeSwitchEnabled}">
+    <hr class="dropdown-divider" aria-hidden="true">
+    <p class="h4 dropdown-headline"><f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:colorScheme" /></p>
+    <typo3-backend-color-scheme-switch data="{colorSchemes -> f:format.json()}" activeColorScheme="{activeColorScheme}"></typo3-backend-color-scheme-switch>
+</f:if>
 <hr class="dropdown-divider" aria-hidden="true">
 <f:if condition="{switchUserMode}">
     <f:then>
diff --git a/typo3/sysext/backend/Resources/Public/Css/backend.css b/typo3/sysext/backend/Resources/Public/Css/backend.css
index 19bdde0151ab..191321c2020a 100644
--- a/typo3/sysext/backend/Resources/Public/Css/backend.css
+++ b/typo3/sysext/backend/Resources/Public/Css/backend.css
@@ -2378,7 +2378,9 @@ button[aria-expanded=true]:not(:disabled) .modulemenu-indicator:after{transform:
 :root{color-scheme:only light;scroll-behavior:smooth;--typo3-font-size:12px;--typo3-font-size-small:11px;--typo3-font-family-sans-serif:Verdana,Arial,Helvetica,sans-serif;--typo3-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--typo3-font-family:var(--typo3-font-family-sans-serif);--typo3-font-family-code:var(--typo3-font-family-monospace);--typo3-spacing:1rem;--typo3-header-font-family:"Source Sans 3",sans-serif;--typo3-zindex-modal-backdrop:1050;--typo3-zindex-modal:1055;--typo3-text-color-base:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-10));--typo3-text-color-link:var(--typo3-text-color-base);--typo3-text-color-variant:light-dark(var(--token-color-neutral-60), var(--token-color-neutral-30));--typo3-text-color-primary:light-dark(var(--token-color-blue-60), var(--token-color-blue-30));--typo3-text-color-secondary:light-dark(var(--token-color-neutral-70), var(--token-color-neutral-35));--typo3-text-color-info:light-dark(var(--token-color-teal-70), var(--token-color-teal-40));--typo3-text-color-success:light-dark(var(--token-color-green-70), var(--token-color-green-50));--typo3-text-color-warning:light-dark(var(--token-color-yellow-80), var(--token-color-yellow-40));--typo3-text-color-danger:light-dark(var(--token-color-red-60), var(--token-color-red-40));--typo3-text-color-code:light-dark(var(--token-color-magenta-60), var(--token-color-magenta-35));--typo3-text-color-notice:light-dark(var(--token-color-neutral-75), var(--token-color-neutral-40));--typo3-text-color-default:var(--typo3-text-color-base);--typo3-surface-dim:light-dark(var(--token-color-neutral-15), var(--token-color-neutral-96));--typo3-surface-base:light-dark(var(--token-color-neutral-4), var(--token-color-neutral-96));--typo3-surface-bright:light-dark(var(--token-color-neutral-4), var(--token-color-neutral-85));--typo3-surface-container-lowest:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-100));--typo3-surface-container-low:light-dark(var(--token-color-neutral-3), var(--token-color-neutral-97));--typo3-surface-container-base:light-dark(var(--token-color-neutral-5), var(--token-color-neutral-95));--typo3-surface-container-high:light-dark(var(--token-color-neutral-10), var(--token-color-neutral-90));--typo3-surface-container-highest:light-dark(var(--token-color-neutral-15), var(--token-color-neutral-85));--typo3-surface-primary:light-dark(var(--token-color-blue-60), var(--token-color-blue-70));--typo3-surface-primary-text:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-surface-container-primary:light-dark(var(--token-color-blue-10), var(--token-color-blue-90));--typo3-surface-container-primary-text:light-dark(var(--token-color-blue-90), var(--token-color-blue-10));--typo3-surface-secondary:light-dark(var(--token-color-neutral-70), var(--token-color-neutral-70));--typo3-surface-secondary-text:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-surface-container-secondary:light-dark(var(--token-color-neutral-10), var(--token-color-neutral-90));--typo3-surface-container-secondary-text:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-10));--typo3-surface-info:light-dark(var(--token-color-teal-20), var(--token-color-teal-70));--typo3-surface-info-text:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-surface-container-info:light-dark(var(--token-color-teal-10), var(--token-color-teal-90));--typo3-surface-container-info-text:light-dark(var(--token-color-teal-90), var(--token-color-teal-10));--typo3-surface-success:light-dark(var(--token-color-green-70), var(--token-color-green-80));--typo3-surface-success-text:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-surface-container-success:light-dark(var(--token-color-green-10), var(--token-color-green-90));--typo3-surface-container-success-text:light-dark(var(--token-color-green-90), var(--token-color-green-10));--typo3-surface-warning:light-dark(var(--token-color-yellow-40), var(--token-color-yellow-80));--typo3-surface-warning-text:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-surface-container-warning:light-dark(var(--token-color-yellow-10), var(--token-color-yellow-90));--typo3-surface-container-warning-text:light-dark(var(--token-color-yellow-90), var(--token-color-yellow-10));--typo3-surface-danger:light-dark(var(--token-color-red-50), var(--token-color-red-70));--typo3-surface-danger-text:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-surface-container-danger:light-dark(var(--token-color-red-10), var(--token-color-red-90));--typo3-surface-container-danger-text:light-dark(var(--token-color-red-90), var(--token-color-red-10));--typo3-surface-notice:light-dark(var(--token-color-neutral-75), var(--token-color-neutral-85));--typo3-surface-notice-text:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-surface-container-notice:light-dark(var(--token-color-neutral-10), var(--token-color-neutral-90));--typo3-surface-container-notice-text:light-dark(var(--token-color-neutral-95), var(--token-color-neutral-0));--typo3-surface-default:light-dark(var(--token-color-neutral-5), var(--token-color-neutral-95));--typo3-surface-default-text:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-surface-container-default:light-dark(var(--token-color-neutral-5), var(--token-color-neutral-95));--typo3-surface-container-default-text:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-state-default-color:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-state-default-bg:light-dark(var(--token-color-neutral-5), var(--token-color-neutral-90));--typo3-state-default-border-color:light-dark(var(--token-color-neutral-25), var(--token-color-neutral-80));--typo3-state-default-hover-color:var(--typo3-state-default-color);--typo3-state-default-hover-bg:light-dark(var(--token-color-neutral-10), var(--token-color-neutral-85));--typo3-state-default-hover-border-color:light-dark(var(--token-color-neutral-35), var(--token-color-neutral-75));--typo3-state-default-focus-color:var(--typo3-state-default-color);--typo3-state-default-focus-bg:light-dark(var(--token-color-neutral-15), var(--token-color-neutral-80));--typo3-state-default-focus-border-color:light-dark(var(--token-color-neutral-40), var(--token-color-neutral-70));--typo3-state-default-disabled-color:var(--typo3-state-default-color);--typo3-state-default-disabled-bg:var(--typo3-state-default-bg);--typo3-state-default-disabled-border-color:var(--typo3-state-default-border-color);--typo3-state-primary-color:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-state-primary-bg:light-dark(var(--token-color-blue-60), var(--token-color-blue-70));--typo3-state-primary-border-color:light-dark(var(--token-color-blue-65), var(--token-color-blue-65));--typo3-state-primary-hover-color:var(--typo3-state-primary-color);--typo3-state-primary-hover-bg:light-dark(var(--token-color-blue-65), var(--token-color-blue-65));--typo3-state-primary-hover-border-color:light-dark(var(--token-color-blue-70), var(--token-color-blue-60));--typo3-state-primary-focus-color:var(--typo3-state-primary-color);--typo3-state-primary-focus-bg:light-dark(var(--token-color-blue-70), var(--token-color-blue-60));--typo3-state-primary-focus-border-color:light-dark(var(--token-color-blue-75), var(--token-color-blue-55));--typo3-state-primary-disabled-color:var(--typo3-state-primary-color);--typo3-state-primary-disabled-bg:var(--typo3-state-primary-bg);--typo3-state-primary-disabled-border-color:var(--typo3-state-default-primary-color);--typo3-state-secondary-color:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-state-secondary-bg:light-dark(var(--token-color-neutral-70), var(--token-color-neutral-70));--typo3-state-secondary-border-color:light-dark(var(--token-color-neutral-75), var(--token-color-neutral-65));--typo3-state-secondary-hover-color:var(--typo3-state-secondary-color);--typo3-state-secondary-hover-bg:light-dark(var(--token-color-neutral-75), var(--token-color-neutral-65));--typo3-state-secondary-hover-border-color:light-dark(var(--token-color-neutral-80), var(--token-color-neutral-60));--typo3-state-secondary-focus-color:var(--typo3-state-secondary-color);--typo3-state-secondary-focus-bg:light-dark(var(--token-color-neutral-80), var(--token-color-neutral-60));--typo3-state-secondary-focus-border-color:light-dark(var(--token-color-neutral-85), var(--token-color-neutral-55));--typo3-state-secondary-disabled-color:var(--typo3-state-secondary-color);--typo3-state-secondary-disabled-bg:var(--typo3-state-secondary-bg);--typo3-state-secondary-disabled-border-color:var(--typo3-state-default-secondary-color);--typo3-state-success-color:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-state-success-bg:light-dark(var(--token-color-green-70), var(--token-color-green-80));--typo3-state-success-border-color:light-dark(var(--token-color-green-75), var(--token-color-green-75));--typo3-state-success-hover-color:var(--typo3-state-success-color);--typo3-state-success-hover-bg:light-dark(var(--token-color-green-75), var(--token-color-green-75));--typo3-state-success-hover-border-color:light-dark(var(--token-color-green-80), var(--token-color-green-70));--typo3-state-success-focus-color:var(--typo3-state-success-color);--typo3-state-success-focus-bg:light-dark(var(--token-color-green-80), var(--token-color-green-70));--typo3-state-success-focus-border-color:light-dark(var(--token-color-green-85), var(--token-color-green-65));--typo3-state-success-disabled-color:var(--typo3-state-success-color);--typo3-state-success-disabled-bg:var(--typo3-state-success-bg);--typo3-state-success-disabled-border-color:var(--typo3-state-default-success-color);--typo3-state-warning-color:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-state-warning-bg:light-dark(var(--token-color-yellow-40), var(--token-color-yellow-80));--typo3-state-warning-border-color:light-dark(var(--token-color-yellow-45), var(--token-color-yellow-75));--typo3-state-warning-hover-color:var(--typo3-state-warning-color);--typo3-state-warning-hover-bg:light-dark(var(--token-color-yellow-45), var(--token-color-yellow-75));--typo3-state-warning-hover-border-color:light-dark(var(--token-color-yellow-50), var(--token-color-yellow-70));--typo3-state-warning-focus-color:var(--typo3-state-warning-color);--typo3-state-warning-focus-bg:light-dark(var(--token-color-yellow-50), var(--token-color-yellow-70));--typo3-state-warning-focus-border-color:light-dark(var(--token-color-yellow-55), var(--token-color-yellow-65));--typo3-state-warning-disabled-color:var(--typo3-state-warning-color);--typo3-state-warning-disabled-bg:var(--typo3-state-warning-bg);--typo3-state-warning-disabled-border-color:var(--typo3-state-warning-border-color);--typo3-state-danger-color:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-state-danger-bg:light-dark(var(--token-color-red-50), var(--token-color-red-70));--typo3-state-danger-border-color:light-dark(var(--token-color-red-55), var(--token-color-red-65));--typo3-state-danger-hover-color:var(--typo3-state-danger-color);--typo3-state-danger-hover-bg:light-dark(var(--token-color-red-55), var(--token-color-red-65));--typo3-state-danger-hover-border-color:light-dark(var(--token-color-red-60), var(--token-color-red-60));--typo3-state-danger-focus-color:var(--typo3-state-danger-color);--typo3-state-danger-focus-bg:light-dark(var(--token-color-red-60), var(--token-color-red-60));--typo3-state-danger-focus-border-color:light-dark(var(--token-color-red-65), var(--token-color-red-55));--typo3-state-danger-disabled-color:var(--typo3-state-danger-color);--typo3-state-danger-disabled-bg:var(--typo3-state-danger-bg);--typo3-state-danger-disabled-border-color:var(--typo3-state-danger-border-color);--typo3-state-info-color:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-0));--typo3-state-info-bg:light-dark(var(--token-color-teal-20), var(--token-color-teal-70));--typo3-state-info-border-color:light-dark(var(--token-color-teal-25), var(--token-color-teal-65));--typo3-state-info-hover-color:var(--typo3-state-info-color);--typo3-state-info-hover-bg:light-dark(var(--token-color-teal-25), var(--token-color-teal-65));--typo3-state-info-hover-border-color:light-dark(var(--token-color-teal-30), var(--token-color-teal-60));--typo3-state-info-focus-color:var(--typo3-state-info-color);--typo3-state-info-focus-bg:light-dark(var(--token-color-teal-30), var(--token-color-teal-60));--typo3-state-info-focus-border-color:light-dark(var(--token-color-teal-35), var(--token-color-teal-55));--typo3-state-info-disabled-color:var(--typo3-state-info-color);--typo3-state-info-disabled-bg:var(--typo3-state-info-bg);--typo3-state-info-disabled-border-color:var(--typo3-state-info-border-color);--typo3-state-notice-color:light-dark(var(--token-color-neutral-0), var(--token-color-neutral-0));--typo3-state-notice-bg:light-dark(var(--token-color-neutral-75), var(--token-color-neutral-85));--typo3-state-notice-border-color:light-dark(var(--token-color-neutral-80), var(--token-color-neutral-80));--typo3-state-notice-hover-color:var(--typo3-state-notice-color);--typo3-state-notice-hover-bg:light-dark(var(--token-color-neutral-80), var(--token-color-neutral-80));--typo3-state-notice-hover-border-color:light-dark(var(--token-color-neutral-85), var(--token-color-neutral-75));--typo3-state-notice-focus-color:var(--typo3-state-notice-color);--typo3-state-notice-focus-bg:light-dark(var(--token-color-neutral-85), var(--token-color-neutral-75));--typo3-state-notice-focus-border-color:light-dark(var(--token-color-neutral-90), var(--token-color-neutral-70));--typo3-state-notice-disabled-color:var(--typo3-state-notice-color);--typo3-state-notice-disabled-bg:var(--typo3-state-notice-bg);--typo3-state-notice-disabled-border-color:var(--typo3-state-notice-border-color);--typo3-shadow-2:0 1px 2px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 1px 2px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-shadow-4:0 2px 4px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 2px 4px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-shadow-8:0 4px 8px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 4px 8px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-shadow-16:0 8px 16px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 8px 16px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-shadow-28:0 14px 28px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 14px 28px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-shadow-64:0 0 4px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.2)),0 32px 64px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.28));--typo3-component-color:var(--typo3-text-color-base);--typo3-component-variant-color:var(--typo3-text-color-variant);--typo3-component-primary-color:var(--typo3-text-color-primary);--typo3-component-secondary-color:var(--typo3-text-color-secondary);--typo3-component-match-highlight-color:inherit;--typo3-component-match-highlight-bg:rgba(234, 92, 0, .33);--typo3-component-bg:var(--typo3-surface-container-lowest);--typo3-component-link-color:light-dark(#05c, #6699e0);--typo3-component-link-hover-color:light-dark(#1a66d1, #80aae6);--typo3-component-font-size:0.75rem;--typo3-component-line-height:1.5;--typo3-component-border-radius:4px;--typo3-component-border-width:1px;--typo3-component-border-color:light-dark(rgb(215, 215, 215), rgb(51, 51, 51));--typo3-component-padding-y:.75rem;--typo3-component-padding-x:1rem;--typo3-component-box-shadow:var(--typo3-shadow-2);--typo3-component-box-shadow-strong:var(--typo3-shadow-4);--typo3-component-box-shadow-tooltip:var(--typo3-shadow-8);--typo3-component-box-shadow-flyout:var(--typo3-shadow-16);--typo3-component-box-shadow-dialog:var(--typo3-shadow-28);--typo3-component-box-shadow-window:var(--typo3-shadow-64);--typo3-component-hover-color:var(--typo3-component-color);--typo3-component-hover-bg:light-dark(#f2f7fc, rgb(51, 51, 51));--typo3-component-hover-border-color:light-dark(#d9e6f7, rgb(90, 90, 90));--typo3-component-focus-color:var(--typo3-component-color);--typo3-component-focus-bg:light-dark(#f2f7fc, #002b66);--typo3-component-focus-border-color:light-dark(#3377d6, #00337a);--typo3-component-active-color:#fff;--typo3-component-active-bg:light-dark(#3377d6, #0048ad);--typo3-component-active-border-color:light-dark(#3377d6, #0044a3);--typo3-component-disabled-color:rgb(115, 115, 115);--typo3-component-disabled-bg:transparent;--typo3-component-disabled-border-color:transparent;--typo3-component-spacing:2rem;--typo3-list-item-padding-y:.5rem;--typo3-list-item-padding-x:.75rem;--typo3-list-item-hover-color:var(--typo3-component-hover-color);--typo3-list-item-hover-bg:var(--typo3-component-hover-bg);--typo3-list-item-hover-border-color:var(--typo3-component-hover-border-color);--typo3-list-item-focus-color:var(--typo3-component-focus-color);--typo3-list-item-focus-bg:var(--typo3-component-focus-bg);--typo3-list-item-focus-border-color:var(--typo3-component-focus-border-color);--typo3-list-item-active-color:var(--typo3-list-item-focus-color);--typo3-list-item-active-bg:var(--typo3-list-item-focus-bg);--typo3-list-item-active-border-color:var(--typo3-list-item-focus-border-color);--typo3-list-item-disabled-color:var(--typo3-component-disabled-color);--typo3-list-item-disabled-bg:var(--typo3-component-disabled-bg);--typo3-list-item-disabled-border-color:var(--typo3-component-disabled-border-color);--typo3-legend-font-weight:600;--typo3-input-font-size:.75rem;--typo3-input-line-height:1.5;--typo3-input-padding-y:.5rem;--typo3-input-padding-x:.75rem;--typo3-input-sm-padding-y:.3125rem;--typo3-input-sm-padding-x:.5rem;--typo3-input-sm-font-size:.6875rem;--typo3-input-border-width:1px;--typo3-input-border-radius:var(--typo3-component-border-radius);--typo3-input-color:var(--typo3-text-color-base);--typo3-input-placeholder-color:color-mix(in srgb, var(--typo3-input-color), transparent 30%);--typo3-input-bg:var(--typo3-surface-container-lowest);--typo3-input-group-addon-bg:color-mix(in srgb, var(--typo3-input-bg), var(--typo3-input-color) 10%);--typo3-input-border-color:var(--typo3-state-default-border-color);--typo3-input-hover-color:var(--typo3-input-color);--typo3-input-hover-bg:var(--typo3-input-bg);--typo3-input-hover-border-color:var(--typo3-state-default-hover-border-color);--typo3-input-focus-color:var(--typo3-input-color);--typo3-input-focus-bg:var(--typo3-input-bg);--typo3-input-focus-border-color:var(--typo3-state-primary-focus-border-color);--typo3-input-active-color:var(--typo3-state-primary-color);--typo3-input-active-bg:var(--typo3-state-primary-bg);--typo3-input-active-border-color:var(--typo3-state-primary-focus-border-color);--typo3-input-disabled-color:var(--typo3-state-default-disabled-color);--typo3-input-disabled-bg:var(--typo3-state-default-disabled-bg);--typo3-input-disabled-border-color:var(--typo3-state-default-disabled-border-color);--typo3-input-disabled-opacity:.65}
 [data-color-scheme=auto]{color-scheme:light dark}
 [data-color-scheme=dark]{color-scheme:only dark}
+[data-color-scheme=dark] .alwan,[data-color-scheme=dark] .context-menu,[data-color-scheme=dark] .dragging-tooltip,[data-color-scheme=dark] .dropdown-menu,[data-color-scheme=dark] .flatpickr-calendar,[data-color-scheme=dark] .nodes-container,[data-color-scheme=dark] .scaffold-content-navigation-drag,[data-color-scheme=dark] .scaffold-content-navigation-switcher,[data-color-scheme=dark] .tree-toolbar{color-scheme:only dark}
 [data-color-scheme=light]{color-scheme:only light}
+[data-color-scheme=light] .alwan,[data-color-scheme=light] .context-menu,[data-color-scheme=light] .dragging-tooltip,[data-color-scheme=light] .dropdown-menu,[data-color-scheme=light] .flatpickr-calendar,[data-color-scheme=light] .nodes-container,[data-color-scheme=light] .scaffold-content-navigation-drag,[data-color-scheme=light] .scaffold-content-navigation-switcher,[data-color-scheme=light] .tree-toolbar{color-scheme:only light}
 :root{--typo3-position-modifier:1;--typo3-position-start:left;--typo3-position-end:right}
 [dir=rtl]{--typo3-position-modifier:-1;--typo3-position-start:right;--typo3-position-end:left}
 .dropdown-menu{--bs-secondary-color:var(--typo3-component-secondary-color)}
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-manager.js b/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-manager.js
new file mode 100644
index 000000000000..2d4eb66a7db7
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-manager.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+var Identifier;!function(e){e.switch="typo3-backend-color-scheme-switch"}(Identifier||(Identifier={}));class ColorSchemeManager{constructor(){document.addEventListener("typo3:color-scheme:update",this.onBroadcastSchemeUpdate.bind(this))}onBroadcastSchemeUpdate(e){const t=e.detail.payload?.name||e.detail.name;document.documentElement.setAttribute("data-color-scheme",t),document.list_frame.document.documentElement.setAttribute("data-color-scheme",t),this.updateActiveScheme(t)}updateActiveScheme(e){document.querySelector(Identifier.switch).activeColorScheme=e}}export default new ColorSchemeManager;
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-switch.js b/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-switch.js
new file mode 100644
index 000000000000..db06969cdcb0
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/color-scheme-switch.js
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+var __decorate=function(e,t,o,r){var c,a=arguments.length,n=a<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,o,r);else for(var i=e.length-1;i>=0;i--)(c=e[i])&&(n=(a<3?c(n):a>3?c(t,o,n):c(t,o))||n);return a>3&&n&&Object.defineProperty(t,o,n),n};import{html,LitElement}from"lit";import{customElement,property}from"lit/decorators.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import{BroadcastMessage}from"@typo3/backend/broadcast-message.js";import BroadcastService from"@typo3/backend/broadcast-service.js";import"@typo3/backend/element/icon-element.js";let ColorSchemeSwitchElement=class extends LitElement{constructor(){super(...arguments),this.activeColorScheme=null,this.data=null}createRenderRoot(){return this}render(){return html`
+      <ul class="dropdown-list">
+        ${this.data.map((e=>this.renderItem(e)))}
+      </ul>
+    `}renderItem(e){return html`
+      <li>
+        <button class="dropdown-item" @click="${()=>this.handleClick(e.value)}" aria-current="${this.activeColorScheme===e.value?"true":"false"}">
+          <span class="dropdown-item-columns">
+            ${this.activeColorScheme===e.value?html`
+              <span class="text-primary">
+                <typo3-backend-icon identifier="actions-dot" size="small"></typo3-backend-icon>
+              </span>
+            `:html`
+              <typo3-backend-icon identifier="empty-empty" size="small"></typo3-backend-icon>
+            `}
+            <span class="dropdown-item-column dropdown-item-column-icon" aria-hidden="true">
+              <typo3-backend-icon identifier="${e.icon}" size="small"></typo3-backend-icon>
+            </span>
+            <span class="dropdown-item-column dropdown-item-column-title">
+              ${e.label}
+            </span>
+            <slot></slot>
+          </span>
+        </button>
+      </li>
+    `}async handleClick(e){this.broadcastSchemeUpdate(e),await this.persistSchemeUpdate(e)}async persistSchemeUpdate(e){const t=new URL(TYPO3.settings.ajaxUrls.color_scheme_update,window.location.origin);return await new AjaxRequest(t).post({colorScheme:e})}broadcastSchemeUpdate(e){const t=new BroadcastMessage("color-scheme","update",{name:e});BroadcastService.post(t),document.dispatchEvent(t.createCustomEvent("typo3"))}};__decorate([property({type:String})],ColorSchemeSwitchElement.prototype,"activeColorScheme",void 0),__decorate([property({type:Array})],ColorSchemeSwitchElement.prototype,"data",void 0),ColorSchemeSwitchElement=__decorate([customElement("typo3-backend-color-scheme-switch")],ColorSchemeSwitchElement);export{ColorSchemeSwitchElement};
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Page/PageRenderer.php b/typo3/sysext/core/Classes/Page/PageRenderer.php
index 1ec6c3324294..55890b91badb 100644
--- a/typo3/sysext/core/Classes/Page/PageRenderer.php
+++ b/typo3/sysext/core/Classes/Page/PageRenderer.php
@@ -283,6 +283,14 @@ class PageRenderer implements SingletonInterface
             if ($this->locale->isRightToLeftLanguageDirection()) {
                 $attributes['dir'] = 'rtl';
             }
+            // TODO: build an API to add HTML attributes cleanly
+            if ($this->getApplicationType() === 'BE' && !empty($GLOBALS['BE_USER'])) {
+                $userTS = $GLOBALS['BE_USER']->getTSConfig();
+
+                if (!isset($userTS['setup.']['fields.']['colorScheme.']['disabled']) || $userTS['setup.']['fields.']['colorScheme.']['disabled'] !== '1') {
+                    $attributes['data-color-scheme'] = $GLOBALS['BE_USER']->uc['colorScheme'] ?? 'auto';
+                }
+            }
             $this->setHtmlTag('<html ' . GeneralUtility::implodeAttributes($attributes, true) . '>');
         }
     }
diff --git a/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104868-AddColorSchemeSwitching.rst  b/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104868-AddColorSchemeSwitching.rst 
new file mode 100644
index 000000000000..5587805ac15b
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/13.3/Feature-104868-AddColorSchemeSwitching.rst 	
@@ -0,0 +1,25 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-104868-1725912804:
+
+=============================================
+Feature: #104868 - Add color scheme switching
+=============================================
+
+See :issue:`104868`
+
+Description
+===========
+
+Options have been added to switch between the available color schemes in TYPO3. A set of buttons
+for each available color scheme in the user dropdown at the top right and a setting in User Settings.
+
+As the dark color scheme is currently regarded experimental until further notice, color scheme switching logic is
+currently hidden behind the UserTS setting :typoscript:`setup.fields.colorScheme.disabled`.
+
+Impact
+======
+
+It is now possible to switch to an automatic, light or dark color scheme for use in the backend.
+
+.. index:: Backend, ext:backend
diff --git a/typo3/sysext/install/Classes/Controller/AbstractController.php b/typo3/sysext/install/Classes/Controller/AbstractController.php
index 03a06ea81561..8e1ef6580171 100644
--- a/typo3/sysext/install/Classes/Controller/AbstractController.php
+++ b/typo3/sysext/install/Classes/Controller/AbstractController.php
@@ -51,6 +51,7 @@ class AbstractController
             'context' => $request->getQueryParams()['install']['context'] ?? '',
             'composerMode' => Environment::isComposerMode(),
             'currentTypo3Version' => (string)(new Typo3Version()),
+            'colorScheme' => $request->getQueryParams()['install']['colorScheme'] ?? '',
         ]);
         return $view;
     }
diff --git a/typo3/sysext/install/Classes/Controller/BackendModuleController.php b/typo3/sysext/install/Classes/Controller/BackendModuleController.php
index 1dfaf382cfc0..2719148bd85d 100644
--- a/typo3/sysext/install/Classes/Controller/BackendModuleController.php
+++ b/typo3/sysext/install/Classes/Controller/BackendModuleController.php
@@ -87,7 +87,7 @@ class BackendModuleController
         $this->sessionService->startSession();
         $this->sessionService->setAuthorizedBackendSession($userSession);
         $entryPointResolver = GeneralUtility::makeInstance(BackendEntryPointResolver::class);
-        $redirectLocation = $entryPointResolver->getUriFromRequest($request, 'install.php')->withQuery('?install[controller]=' . $controller . '&install[context]=backend');
+        $redirectLocation = $entryPointResolver->getUriFromRequest($request, 'install.php')->withQuery('?install[controller]=' . $controller . '&install[context]=backend' . '&install[colorScheme]=' . ($this->getBackendUser()->uc['colorScheme'] ?? ''));
         return new RedirectResponse($redirectLocation, 303);
     }
 
diff --git a/typo3/sysext/install/Resources/Private/Templates/Layout/Init.html b/typo3/sysext/install/Resources/Private/Templates/Layout/Init.html
index 59d9afac3ef2..adabff7868f3 100644
--- a/typo3/sysext/install/Resources/Private/Templates/Layout/Init.html
+++ b/typo3/sysext/install/Resources/Private/Templates/Layout/Init.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" {f:if(condition: colorScheme, then: 'data-color-scheme="{colorScheme}"')}>
 <head>
     <title>Install Tool on site {siteName}</title>
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
diff --git a/typo3/sysext/setup/Configuration/user.tsconfig b/typo3/sysext/setup/Configuration/user.tsconfig
new file mode 100644
index 000000000000..9ea482816db4
--- /dev/null
+++ b/typo3/sysext/setup/Configuration/user.tsconfig
@@ -0,0 +1 @@
+setup.fields.colorScheme.disabled = 1
diff --git a/typo3/sysext/setup/ext_tables.php b/typo3/sysext/setup/ext_tables.php
index 2a7b63742848..981384387f39 100644
--- a/typo3/sysext/setup/ext_tables.php
+++ b/typo3/sysext/setup/ext_tables.php
@@ -2,6 +2,7 @@
 
 declare(strict_types=1);
 
+use TYPO3\CMS\Backend\Backend\ColorScheme;
 use TYPO3\CMS\Setup\Controller\SetupModuleController;
 
 defined('TYPO3') or die();
@@ -94,10 +95,15 @@ $GLOBALS['TYPO3_USER_SETTINGS'] = [
                 'sitenameFirst' => 'LLL:EXT:setup/Resources/Private/Language/locallang.xlf:backendTitleFormat.sitenameFirst',
             ],
         ],
+        'colorScheme' => [
+            'type' => 'select',
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:colorScheme',
+            'items' => ColorScheme::getAvailableItemsForSelection(),
+        ],
     ],
     'showitem' => '--div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:personal_data,realName,email,emailMeAtLogin,avatar,lang,
             --div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:accountSecurity,passwordCurrent,password,password2,mfaProviders,
-            --div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:opening,startModule,backendTitleFormat,
+            --div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:opening,colorScheme,startModule,backendTitleFormat,
             --div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:editFunctionsTab,titleLen,edit_docModuleUpload,showHiddenFilesAndFolders,copyLevels,
             --div--;LLL:EXT:setup/Resources/Private/Language/locallang.xlf:resetTab,resetConfiguration',
 ];
-- 
GitLab