diff --git a/src_assets/common/assets/web/Navbar.vue b/src_assets/common/assets/web/Navbar.vue
index 9e4e1be64f5..838c630f45a 100644
--- a/src_assets/common/assets/web/Navbar.vue
+++ b/src_assets/common/assets/web/Navbar.vue
@@ -1,60 +1,89 @@
 <template>
-    <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400">
-        <div class="container-fluid">
-            <a class="navbar-brand" href="/" title="Sunshine">
-                <img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
-            </a>
-            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
-                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-                <span class="navbar-toggler-icon"></span>
-            </button>
-            <div class="collapse navbar-collapse" id="navbarSupportedContent">
-                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-                    <li class="nav-item">
-                        <a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
-                    </li>
-                </ul>
-            </div>
-        </div>
-    </nav>
+  <nav class="navbar navbar-light navbar-expand-lg navbar-background header">
+    <div class="container-fluid">
+      <a class="navbar-brand" href="/" title="Sunshine">
+        <img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
+      </a>
+      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
+              aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+      </button>
+      <div class="collapse navbar-collapse" id="navbarSupportedContent">
+        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+          <li class="nav-item">
+            <a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> {{ $t('navbar.home') }}</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> {{ $t('navbar.pin') }}</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> {{ $t('navbar.applications') }}</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> {{ $t('navbar.configuration') }}</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> {{ $t('navbar.password') }}</a>
+          </li>
+          <li class="nav-item">
+            <a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
+          </li>
+          <li class="nav-item">
+            <ThemeToggle/>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </nav>
 </template>
 
 <script>
+import ThemeToggle from './ThemeToggle.vue'
+
 export default {
-    created() {
-        console.log("Header mounted!")
-    },
-    mounted() {
-        let el = document.querySelector("a[href='" + document.location.pathname + "']");
-        if (el) el.classList.add("active")
-        let discordWidget = document.createElement('script')
-        discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
-        document.head.appendChild(discordWidget)
-    }
+  components: { ThemeToggle },
+  created() {
+    console.log("Header mounted!")
+  },
+  mounted() {
+    let el = document.querySelector("a[href='" + document.location.pathname + "']");
+    if (el) el.classList.add("active")
+    let discordWidget = document.createElement('script')
+    discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
+    document.head.appendChild(discordWidget)
+  }
 }
 </script>
 
 <style>
-.nav-link.active {
-    font-weight: 500;
+.navbar-background {
+  background-color: #ffc400
+}
+
+.header .nav-link {
+  color: rgba(0, 0, 0, .65) !important;
+}
+
+.header .nav-link.active {
+  color: rgb(0, 0, 0) !important;
+  font-weight: 500;
+}
+
+.header .nav-link:hover {
+  color: rgb(0, 0, 0) !important;
+  font-weight: 500;
+}
+
+.header .navbar-toggler {
+  color: rgba(var(--bs-dark-rgb), .65) !important;
+  border: var(--bs-border-width) solid rgba(var(--bs-dark-rgb), 0.15) !important;
+}
+
+.header .navbar-toggler-icon {
+  --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
 }
 
 .form-control::placeholder {
-    opacity: 0.5;
+  opacity: 0.5;
 }
 </style>
diff --git a/src_assets/common/assets/web/ThemeToggle.vue b/src_assets/common/assets/web/ThemeToggle.vue
new file mode 100644
index 00000000000..7c34916adc9
--- /dev/null
+++ b/src_assets/common/assets/web/ThemeToggle.vue
@@ -0,0 +1,46 @@
+<script setup>
+import { loadAutoTheme, setupThemeToggleListener } from './theme'
+import { onMounted } from 'vue'
+
+onMounted(() => {
+  loadAutoTheme()
+  setupThemeToggleListener()
+})
+</script>
+
+<template>
+  <div class="dropdown bd-mode-toggle">
+    <a class="nav-link dropdown-toggle align-items-center"
+            id="bd-theme"
+            type="button"
+            aria-expanded="false"
+            data-bs-toggle="dropdown"
+            aria-label="{{ $t('navbar.toggle_theme') }} ({{ $t('navbar.theme_auto') }})">
+      <span class="bi my-1 theme-icon-active"><i class="fa-solid fa-circle-half-stroke"></i></span>
+      <span id="bd-theme-text">{{ $t('navbar.toggle_theme') }}</span>
+    </a>
+    <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
+      <li>
+        <button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
+          <i class="bi me-2 theme-icon fas fa-fw fa-solid fa-sun"></i>
+          {{ $t('navbar.theme_light') }}
+        </button>
+      </li>
+      <li>
+        <button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
+          <i class="bi me-2 theme-icon fas fa-fw fa-solid fa-moon"></i>
+          {{ $t('navbar.theme_dark') }}
+        </button>
+      </li>
+      <li>
+        <button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
+          <i class="bi me-2 theme-icon fas fa-fw fa-solid fa-circle-half-stroke"></i>
+          {{ $t('navbar.theme_auto') }}
+        </button>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<style scoped>
+</style>
diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html
index 0fd0651aa13..9a8c65d16ae 100644
--- a/src_assets/common/assets/web/apps.html
+++ b/src_assets/common/assets/web/apps.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
       <%- header %>
@@ -355,10 +355,10 @@ <h4>{{ $t('apps.env_vars_about') }}</h4>
   </div>
 </body>
 <script type="module">
-  import { createApp } from 'vue';
+  import { createApp } from 'vue'
   import { initApp } from './init'
   import Navbar from './Navbar.vue'
-  import {Dropdown} from 'bootstrap'
+  import { Dropdown } from 'bootstrap/dist/js/bootstrap'
 
   const app = createApp({
     components: {
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index 722d55c7600..7df3880ceb4 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
   <%- header %>
@@ -13,12 +13,6 @@
       .buttons {
         padding: 1em 0;
       }
-
-      .ms-item {
-        background-color: #ccc;
-        font-size: 12px;
-        font-weight: bold;
-      }
     </style>
 </head>
 
diff --git a/src_assets/common/assets/web/configs/tabs/AudioVideo.vue b/src_assets/common/assets/web/configs/tabs/AudioVideo.vue
index 851e1e03a7e..58695ba1d2a 100644
--- a/src_assets/common/assets/web/configs/tabs/AudioVideo.vue
+++ b/src_assets/common/assets/web/configs/tabs/AudioVideo.vue
@@ -87,3 +87,6 @@ const config = ref(props.config)
 
   </div>
 </template>
+
+<style scoped>
+</style>
diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue
index 74bd5d9f87b..7fb5ca3b0f9 100644
--- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue
+++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue
@@ -65,3 +65,11 @@ const fpsIn = ref("")
     <div class="form-text">{{ $t('config.res_fps_desc') }}</div>
   </div>
 </template>
+
+<style scoped>
+.ms-item {
+  background-color: var(--bs-dark-bg-subtle);
+  font-size: 12px;
+  font-weight: bold;
+}
+</style>
diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html
index 9aeb4e399b5..d279f16ddc0 100644
--- a/src_assets/common/assets/web/index.html
+++ b/src_assets/common/assets/web/index.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
   <%- header %>
diff --git a/src_assets/common/assets/web/init.js b/src_assets/common/assets/web/init.js
index 3f30a0f034e..22b84df293e 100644
--- a/src_assets/common/assets/web/init.js
+++ b/src_assets/common/assets/web/init.js
@@ -1,5 +1,10 @@
 import i18n from './locale'
 
+// must import even if not implicitly using here
+// https://github.com/aurelia/skeleton-navigation/issues/894
+// https://discourse.aurelia.io/t/bootstrap-import-bootstrap-breaks-dropdown-menu-in-navbar/641/9
+import 'bootstrap/dist/js/bootstrap'
+
 export function initApp(app, config) {
     //Wait for locale initialization, then render
     i18n().then(i18n => {
diff --git a/src_assets/common/assets/web/password.html b/src_assets/common/assets/web/password.html
index 9a47cc565c8..639c82b7401 100644
--- a/src_assets/common/assets/web/password.html
+++ b/src_assets/common/assets/web/password.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
   <%- header %>
diff --git a/src_assets/common/assets/web/pin.html b/src_assets/common/assets/web/pin.html
index 359c5e527ba..f3139e123cc 100644
--- a/src_assets/common/assets/web/pin.html
+++ b/src_assets/common/assets/web/pin.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
   <%- header %>
diff --git a/src_assets/common/assets/web/public/assets/css/sunshine.css b/src_assets/common/assets/web/public/assets/css/sunshine.css
index de2acffee46..843600feebd 100644
--- a/src_assets/common/assets/web/public/assets/css/sunshine.css
+++ b/src_assets/common/assets/web/public/assets/css/sunshine.css
@@ -2,3 +2,15 @@
 [v-cloak] {
     display: none;
 }
+
+[data-bs-theme=dark] .element {
+    color: var(--bs-primary-text-emphasis);
+    background-color: var(--bs-primary-bg-subtle);
+}
+
+@media (prefers-color-scheme: dark) {
+    .element {
+        color: var(--bs-primary-text-emphasis);
+        background-color: var(--bs-primary-bg-subtle);
+    }
+}
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json
index 7c572cedd80..ac69860c305 100644
--- a/src_assets/common/assets/web/public/assets/locale/en.json
+++ b/src_assets/common/assets/web/public/assets/locale/en.json
@@ -337,6 +337,10 @@
     "home": "Home",
     "password": "Change Password",
     "pin": "Pin",
+    "theme_auto": "Auto",
+    "theme_dark": "Dark",
+    "theme_light": "Light",
+    "toggle_theme": "Theme",
     "troubleshoot": "Troubleshooting"
   },
   "password": {
diff --git a/src_assets/common/assets/web/template_header.html b/src_assets/common/assets/web/template_header.html
index 54109ca5255..e636a8ac9e2 100644
--- a/src_assets/common/assets/web/template_header.html
+++ b/src_assets/common/assets/web/template_header.html
@@ -7,4 +7,3 @@
 <link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
 <link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
 <link href="/assets/css/sunshine.css" rel="stylesheet" />
-<script type="module" src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>
diff --git a/src_assets/common/assets/web/theme.js b/src_assets/common/assets/web/theme.js
new file mode 100644
index 00000000000..a1f497802fa
--- /dev/null
+++ b/src_assets/common/assets/web/theme.js
@@ -0,0 +1,84 @@
+const getStoredTheme = () => localStorage.getItem('theme')
+const setStoredTheme = theme => localStorage.setItem('theme', theme)
+
+export const getPreferredTheme = () => {
+    const storedTheme = getStoredTheme()
+    if (storedTheme) {
+        return storedTheme
+    }
+
+    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
+}
+
+const setTheme = theme => {
+    if (theme === 'auto') {
+        document.documentElement.setAttribute(
+            'data-bs-theme',
+            (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
+        )
+    } else {
+        document.documentElement.setAttribute('data-bs-theme', theme)
+    }
+}
+
+export const showActiveTheme = (theme, focus = false) => {
+    const themeSwitcher = document.querySelector('#bd-theme')
+
+    if (!themeSwitcher) {
+        return
+    }
+
+    const themeSwitcherText = document.querySelector('#bd-theme-text')
+    const activeThemeIcon = document.querySelector('.theme-icon-active i')
+    const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
+    const classListOfActiveBtn = btnToActive.querySelector('i').classList
+
+    document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
+        element.classList.remove('active')
+        element.setAttribute('aria-pressed', 'false')
+    })
+
+    btnToActive.classList.add('active')
+    btnToActive.setAttribute('aria-pressed', 'true')
+    activeThemeIcon.classList.remove(...activeThemeIcon.classList.values())
+    activeThemeIcon.classList.add(...classListOfActiveBtn)
+    const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.textContent.trim()})`
+    themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
+
+    if (focus) {
+        themeSwitcher.focus()
+    }
+}
+
+export function setupThemeToggleListener() {
+    document.querySelectorAll('[data-bs-theme-value]')
+        .forEach(toggle => {
+            toggle.addEventListener('click', () => {
+                const theme = toggle.getAttribute('data-bs-theme-value')
+                setStoredTheme(theme)
+                setTheme(theme)
+                showActiveTheme(theme, true)
+            })
+        })
+
+    showActiveTheme(getPreferredTheme(), false)
+}
+
+export function loadAutoTheme() {
+    (() => {
+        'use strict'
+
+        setTheme(getPreferredTheme())
+
+        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
+            const storedTheme = getStoredTheme()
+            if (storedTheme !== 'light' && storedTheme !== 'dark') {
+                setTheme(getPreferredTheme())
+            }
+        })
+
+        window.addEventListener('DOMContentLoaded', () => {
+            showActiveTheme(getPreferredTheme())
+        })
+    })()
+}
diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html
index 00497741368..0adc16542af 100644
--- a/src_assets/common/assets/web/troubleshooting.html
+++ b/src_assets/common/assets/web/troubleshooting.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
       <%- header %>
diff --git a/src_assets/common/assets/web/welcome.html b/src_assets/common/assets/web/welcome.html
index cf1e74ba8e6..18c67b2ee79 100644
--- a/src_assets/common/assets/web/welcome.html
+++ b/src_assets/common/assets/web/welcome.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="en" data-bs-theme="auto">
 
 <head>
   <%- header %>