Dark/Light Mode with Cookie
A theme toggle button that flips a data-theme-status attribute between "light" and "dark", persisting the choice in a JS-Cookie. Includes a Shift+T keyboard shortcut, CSS icon swap animation, and a localStorage alternative variant.
Setup — External Scripts
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>Code
<button data-theme-toggle="" class="btn-darklight">
<div class="btn-darklight__icon">
<div class="btn-darklight__icon-box">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 24 24" fill="none"><path d="M15.5355 8.46447C17.4882 10.4171 17.4882 13.5829 15.5355 15.5355C13.5829 17.4882 10.4171 17.4882 8.46447 15.5355C6.51184 13.5829 6.51184 10.4171 8.46447 8.46447C10.4171 6.51184 13.5829 6.51184 15.5355 8.46447Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4V2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 22V20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18.3599 5.63999L19.0699 4.92999" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M4.93018 19.07L5.64018 18.36" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20 12H22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M2 12H4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18.3599 18.36L19.0699 19.07" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M4.93018 4.92999L5.64018 5.63999" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</div>
<div class="btn-darklight__icon-box is--absolute">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 24 24" fill="none"><path d="M18.395 13.027C18.725 12.872 19.077 13.197 18.985 13.55C18.671 14.752 18.054 15.896 17.104 16.846C14.283 19.667 9.77001 19.726 7.02201 16.978C4.27401 14.23 4.33401 9.71601 7.15501 6.89501C8.10501 5.94501 9.24801 5.32801 10.451 5.01401C10.804 4.92201 11.128 5.27401 10.974 5.60401C9.97201 7.74301 10.301 10.305 11.998 12.002C13.694 13.7 16.256 14.029 18.395 13.027Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
</div>
</div>
<div class="btn-darklight__word">
<p class="btn-darklight__word-p">Light</p>
<p class="btn-darklight__word-p is--absolute">Dark</p>
</div>
<p class="btn-darklight__word-p">mode</p>
</button>.btn-darklight {
color: #131313;
cursor: pointer;
background-color: #efeeec;
border: 0 solid transparent;
border-radius: .25em;
outline: 0 transparent;
justify-content: center;
align-items: center;
padding: 0 1.125em 0 .75em;
display: flex;
position: relative;
overflow: hidden;
}
.btn-darklight__icon {
width: 1.25em;
height: 100%;
margin-right: .25em;
position: relative;
}
.btn-darklight__word {
padding-right: .25em;
position: relative;
}
.btn-darklight__word-p {
margin-top: .05em;
margin-bottom: 0;
line-height: 1.2;
position: relative;
}
.btn-darklight__word-p.is--absolute {
opacity: 0;
letter-spacing: .025em;
position: absolute;
top: 0;
}
.btn-darklight__icon-box {
height: 100%;
padding-top: .66em;
padding-bottom: .66em;
display: flex;
position: relative;
}
.btn-darklight__icon-box.is--absolute {
position: absolute;
}
/* Background */
[data-theme-status] {
transition: background-color 0.4s cubic-bezier(0.35, 1, 0.6, 1);
}
[data-theme-status="dark"] {
background-color: #070915 !important;
}
/* Button Word */
[data-theme-status="dark"] .btn-darklight .btn-darklight__word .btn-darklight__word-p,
[data-theme-status="light"] .btn-darklight .btn-darklight__word .btn-darklight__word-p.is--absolute {
opacity: 0;
}
[data-theme-status="light"] .btn-darklight .btn-darklight__word .btn-darklight__word-p,
[data-theme-status="dark"] .btn-darklight .btn-darklight__word .btn-darklight__word-p.is--absolute {
opacity: 1;
}
/* Button Icon */
.btn-darklight .btn-darklight__icon-box {
transition: transform 0.8s cubic-bezier(0.35, 1.5, 0.6, 1);
transform: translateY(0%) rotate(-90deg);
}
[data-theme-status="dark"] .btn-darklight .btn-darklight__icon-box {
transform: translateY(-100%) rotate(0.001deg);
}function initCookieDarkLight() {
// Function to toggle theme
function initThemeCheck() {
const dashThemeElement = document.querySelector('[data-theme-status]');
if (!dashThemeElement) return;
const currentTheme = dashThemeElement.getAttribute('data-theme-status');
const newTheme = (currentTheme === 'light') ? 'dark' : 'light';
dashThemeElement.setAttribute('data-theme-status', newTheme);
Cookies.set('theme', newTheme, { expires: 365 });
}
// Shift+T keyboard shortcut
document.addEventListener('keydown', function(e) {
const tagName = e.target.tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea' || e.target.isContentEditable) {
return; // Do nothing if typing into a field
}
if (e.shiftKey && e.keyCode === 84) { // Shift+T
e.preventDefault();
initThemeCheck();
}
});
// Click handler for all toggle buttons
document.querySelectorAll('[data-theme-toggle]').forEach(function(button) {
button.addEventListener('click', initThemeCheck);
});
// Restore theme from cookie on load
if (Cookies.get('theme') === 'dark') {
const themeElement = document.querySelector('[data-theme-status]');
if (themeElement) {
themeElement.setAttribute('data-theme-status', 'dark');
}
}
}
// Initialize Cookie Dark/Light Theme
document.addEventListener('DOMContentLoaded', function() {
initCookieDarkLight();
});Guide
Attributes
Add data-theme-status="light" to the <body> element to set the initial theme. Use data-theme-toggle on any button or element to wire up the click handler. The script toggles data-theme-status between "light" and "dark" and writes the result to a cookie.
Cookie Persistence
The user's preference is stored in a cookie named "theme" with a 365-day expiry using the JS-Cookie library. On the next page load the script reads the cookie and restores the saved theme before the page is visible.
Keyboard Shortcut
Pressing Shift+T toggles the theme from anywhere on the page. The shortcut is suppressed when focus is inside an input, textarea, or contenteditable element to avoid interfering with typing.
Icon Animation
The button contains two stacked icon boxes (sun and moon). The CSS transitions translateY and rotation when data-theme-status changes, sliding the active icon into view. The word label ("Light" / "Dark") fades between the two states using opacity.
Styling Dark Mode
Target [data-theme-status="dark"] in CSS to apply dark-mode styles to any element on the page. Use CSS custom properties (e.g. var(--color-dark)) for colour tokens to keep overrides manageable.
localStorage Alternative
To avoid the JS-Cookie dependency, replace Cookies.set('theme', newTheme, { expires: 365 }) with localStorage.setItem('theme', newTheme), and replace Cookies.get('theme') === 'dark' with localStorage.getItem('theme') === 'dark'.