diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Base/ControllerBase.php b/src/opnsense/mvc/app/controllers/OPNsense/Base/ControllerBase.php
index 44cafc3dcf..a6e2cd0489 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/Base/ControllerBase.php
+++ b/src/opnsense/mvc/app/controllers/OPNsense/Base/ControllerBase.php
@@ -383,14 +383,19 @@ class ControllerBase extends ControllerRoot
$this->view->menuBreadcrumbs = $menu->getBreadcrumbs();
// set theme in ui_theme template var, let template handle its defaults (if there is no theme).
- if (
- $cnf->object()->theme->count() > 0 && !empty($cnf->object()->theme) &&
+ $theme_config = (string)$cnf->object()->theme;
+
+ if ($theme_config === 'opnsense-auto') {
+ $this->view->ui_theme = 'opnsense';
+ $this->view->theme_auto = true;
+ } elseif (
+ $cnf->object()->theme->count() > 0 && !empty($theme_config) &&
(
- is_dir('/usr/local/opnsense/www/themes/' . (string)$cnf->object()->theme) ||
+ is_dir('/usr/local/opnsense/www/themes/' . $theme_config) ||
!is_dir('/usr/local/opnsense/www/themes')
)
) {
- $this->view->ui_theme = $cnf->object()->theme;
+ $this->view->ui_theme = $theme_config;
}
// parse product properties, use template (.in) when not found
diff --git a/src/opnsense/mvc/app/views/layouts/default.volt b/src/opnsense/mvc/app/views/layouts/default.volt
index 1403586a4e..eb3feb3866 100644
--- a/src/opnsense/mvc/app/views/layouts/default.volt
+++ b/src/opnsense/mvc/app/views/layouts/default.volt
@@ -215,6 +215,11 @@
+
+ {% if theme_auto is defined and theme_auto %}
+
+ {% endif %}
+
diff --git a/src/opnsense/www/js/opnsense_theme_auto.js b/src/opnsense/www/js/opnsense_theme_auto.js
new file mode 100644
index 0000000000..f06104c4cb
--- /dev/null
+++ b/src/opnsense/www/js/opnsense_theme_auto.js
@@ -0,0 +1,59 @@
+ /*
+ * Copyright (C) 2026 Konstantinos Spartalis
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+(function() {
+ if (window.opnsenseAutoThemeEnabled || document.cookie.indexOf('opnsense_theme=opnsense-auto') !== -1) {
+
+ function updateTheme() {
+ var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ var activeTheme = isDark ? 'opnsense-dark' : 'opnsense';
+
+ var links = document.getElementsByTagName('link');
+ for (var i = 0; i < links.length; i++) {
+ if (links[i].href && links[i].href.indexOf('/ui/themes/') !== -1) {
+ links[i].href = links[i].href.replace(/\/ui\/themes\/[^\/]+\//, '/ui/themes/' + activeTheme + '/');
+ }
+ }
+
+ var imgs = document.getElementsByTagName('img');
+ for (var j = 0; j < imgs.length; j++) {
+ if (imgs[j].src && imgs[j].src.indexOf('/ui/themes/') !== -1) {
+ imgs[j].src = imgs[j].src.replace(/\/ui\/themes\/[^\/]+\//, '/ui/themes/' + activeTheme + '/');
+ }
+ }
+ if (typeof d3 !== 'undefined' && typeof nv !== 'undefined') {
+ window.dispatchEvent(new Event('resize'));
+ }
+ }
+
+ updateTheme();
+ document.addEventListener('DOMContentLoaded', updateTheme);
+
+ if (window.matchMedia) {
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme);
+ }
+ }
+})();
\ No newline at end of file
diff --git a/src/www/authgui.inc b/src/www/authgui.inc
index 67cf31d9b1..5e9662dfee 100644
--- a/src/www/authgui.inc
+++ b/src/www/authgui.inc
@@ -336,6 +336,12 @@ function display_error_form($text)
+
+
+
+
+
+