Setting Up a Dark/Light Theme Toggle for 1C-Bitrix
A theme toggle is a small UI component — a button or switch in the site header that changes the theme when clicked. The task is solved entirely on the frontend and requires no changes to Bitrix PHP code, only correct integration into the site template.
Toggle HTML Markup
A simple toggle with sun and moon icons:
<button
id="theme-toggle"
class="theme-toggle"
aria-label="Switch theme"
title="Switch theme"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="20" height="20">
<!-- sun icon -->
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="20" height="20">
<!-- moon icon -->
</svg>
</button>
CSS for states:
.theme-toggle .icon-moon { display: none; }
.theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: none; }
Switching JavaScript
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
// If theme needs to be saved server-side for authenticated users:
if (window.BX && BX.ajax) {
BX.ajax.post('/local/ajax/save-theme.php', {theme: next});
}
});
Embedding in the Bitrix Template
In the site template's header.php, place the button at the desired location in the header. The inline theme detection script (to prevent FOUC) must come first in <head>:
<!-- In <head>, before loading styles -->
<script>
(function() {
var t = localStorage.getItem('theme')
|| (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', t);
document.documentElement.classList.add('theme-ready');
})();
</script>
The theme-ready class helps disable transition animations on first load:
/* Disable animation before theme is ready */
html:not(.theme-ready) * { transition: none !important; }
Smooth Theme Transition
To avoid an abrupt theme switch:
body,
body *:not(.no-transition) {
transition: background-color 0.2s ease, color 0.2s ease,
border-color 0.2s ease, box-shadow 0.2s ease;
}
Only add transitions for specific colour CSS properties — not transition: all, otherwise content animations will be sluggish.
Saving the Theme for Authenticated Users
For authenticated users, the theme can be stored in the profile so it is applied immediately during server rendering without a flash:
// /local/ajax/save-theme.php
if ($USER->IsAuthorized()) {
$theme = in_array($_POST['theme'], ['dark', 'light']) ? $_POST['theme'] : 'light';
CUser::Update($USER->GetID(), ['UF_THEME' => $theme]);
}
In header.php:
<?php
$savedTheme = 'light';
if ($USER->IsAuthorized()) {
$userFields = CUser::GetByID($USER->GetID())->Fetch();
$savedTheme = $userFields['UF_THEME'] ?? 'light';
}
?>
<html data-theme="<?= htmlspecialchars($savedTheme) ?>">
In this case, the inline script in <head> is still needed — it overrides the server value with the localStorage preference for unauthenticated users.
| Stage | Time |
|---|---|
| Toggle markup and styles | 1–2 h |
| JS switching logic + localStorage | 1–2 h |
| FOUC prevention (inline script) | 1 h |
| Smooth transitions (CSS transitions) | 1 h |
| Saving in the user profile (optional) | 2–3 h |

