diff --git a/airflow/www/static/css/bootstrap-theme.css b/airflow/www/static/css/bootstrap-theme.css
index 0b1568e8608cf..89133959c4bea 100644
--- a/airflow/www/static/css/bootstrap-theme.css
+++ b/airflow/www/static/css/bootstrap-theme.css
@@ -36,6 +36,24 @@ html {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
+html[data-color-scheme="dark"] {
+ filter: invert(100%) hue-rotate(180deg);
+}
+html[data-color-scheme="dark"] #dark_icon {
+ display: block;
+}
+html[data-color-scheme="dark"] #light_icon {
+ display: none;
+}
+html[data-color-scheme="light"] {
+ /* not do */
+}
+html[data-color-scheme="light"] #dark_icon {
+ display: none;
+}
+html[data-color-scheme="light"] #light_icon {
+ display: block;
+}
body {
margin: 0;
}
diff --git a/airflow/www/static/js/toggle_theme.js b/airflow/www/static/js/toggle_theme.js
new file mode 100644
index 0000000000000..292c4995447c7
--- /dev/null
+++ b/airflow/www/static/js/toggle_theme.js
@@ -0,0 +1,46 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* global document, localStorage */
+
+const STORAGE_THEME_KEY = "darkTheme";
+const HTML_THEME_DATASET_KEY = "data-color-scheme";
+const HTML = document.documentElement;
+const TOGGLE_BUTTON_ID = "themeToggleButton";
+
+const getJsonFromStorage = (key) => JSON.parse(localStorage.getItem(key));
+const updateTheme = (isDark) => {
+ localStorage.setItem(STORAGE_THEME_KEY, isDark);
+ HTML.setAttribute(HTML_THEME_DATASET_KEY, isDark ? "dark" : "light");
+};
+const initTheme = () => {
+ const isDark = getJsonFromStorage(STORAGE_THEME_KEY);
+ if (isDark !== null) updateTheme(isDark);
+};
+const toggleTheme = () => {
+ const isDark = getJsonFromStorage(STORAGE_THEME_KEY);
+ updateTheme(!isDark);
+};
+document.addEventListener("DOMContentLoaded", () => {
+ const themeButton = document.getElementById(TOGGLE_BUTTON_ID);
+ themeButton.addEventListener("click", () => {
+ toggleTheme();
+ });
+});
+initTheme();
diff --git a/airflow/www/templates/airflow/main.html b/airflow/www/templates/airflow/main.html
index 69daa701d2651..58383651b7d8a 100644
--- a/airflow/www/templates/airflow/main.html
+++ b/airflow/www/templates/airflow/main.html
@@ -29,6 +29,7 @@
{% endblock %}
{% block head_meta %}
+
{{ super() }}
{% if scheduler_job is defined and (scheduler_job and scheduler_job.is_alive()) %}
@@ -114,7 +115,6 @@
{% block tail_js %}
{{ super() }}
-