How to toggle True or False in sessionStorage - javascript

I am trying to add dark-mode to the website. When someone clicks the dark mode button it will add additional CSS classes and remove them when the button is clicked again.
I know I can do it easily with toggleClass but I don't want to disable this automatically when the page refreshes or some other page is opened on the website.
I have been playing with sessionStorage but unable to succeed so far I have come up with this code here:
Dark Mode
<div class="header-wrap">
Testing toggle with session
</div>
$('.darkmode-button').click(function() {
if (sessionStorage.getItem('darkmode', 'true')) {
$('.header-wrap').removeClass('dark-header');
sessionStorage.setItem('darkmode', 'false');
}
if (sessionStorage.getItem('darkmode', 'false')) {
$('.header-wrap').addClass('dark-header');
sessionStorage.setItem('darkmode', 'true');
}
});
function changeText() {
var x = document.getElementById("dmbutton");
if (x.innerHTML === "Dark Mode") {
x.innerHTML = "Light Mode";
} else {
x.innerHTML = "Dark Mode";
}
}
.header-wrap {
color: black;
}
.dark-header {
color: white;
background-color: black;
}
Can someone please share a working example of how it can be achieved?
I already created a question before but it was marked duplicate with this answer. I read it all but still could not figure it out.

To do what you require simply set a single class on a parent element, the body would work well in this case, to indicate when dark mode has been turned on. You can then use this class in all the relevant selectors in your CSS to update the UI.
Regarding the session storage logic, set a boolean flag when the dark mode is updated when the button is clicked and set the class on the body based on the session storage flag when the page loads.
Putting it all together would look something like this:
Dark Mode
<div class="header-wrap">
Testing toggle with session
</div>
let body = document.body;
let dmButton = document.querySelector('#dmbutton');
dmButton.addEventListener('click', e => {
body.classList.toggle('dark');
sessionStorage.setItem('darkmode', body.classList.contains('dark'));
e.target.textContent = e.target.textContent.trim() === 'Dark Mode' ? 'Light Mode' : 'Dark Mode';
});
let darkModeEnabled = JSON.parse(sessionStorage.getItem('darkmode')); // boolean type coercion
if (darkModeEnabled) {
body.classList.add('dark');
dmButton.textContent = 'Light Mode';
}
.header-wrap {
color: black;
}
body.dark {
background-color: #666;
}
body.dark .header-wrap {
color: white;
background-color: black;
}
Here's a working example in a jsFiddle, as SO snippets are sandboxed and disallow local/session storage access.

I don't know the logic of this code but it works for me which I found from this solution thanks to Stackoverflow
Dark Mode
<div class="header-wrap">
Testing toggle with session
</div>
var $dark = $('.header-wrap')
if (localStorage.getItem('darkmode') === 'true') {
$dark.addClass('dark-header');
}
$('.darkmode-button').click(function() {
$dark.toggleClass('dark-header');
localStorage.setItem('darkmode', $dark.hasClass('dark-header'));
});
.header-wrap {
color: black;
}
.dark-header {
color: white;
background-color: black;

Related

4 Javascript commands execute simultaneously when 1 button is clicked

I have four html elements on a page that are each meant to execute a different setTheme JS command when clicked. However, when I click one, nothing happens. I'm pretty sure they all execute at the same time, and because they're all basically the same code, they cancel each other out and it reverts back to the default theme, which means I see nothing. However, I could be wrong. How do I fix this?
A note about this code: I couldn't figure this out at first, so I looked online but couldn't find anything, so I modified some code meant for the toggleTheme function, not the setTheme function. If that's the problem, I don't know how to solve it but it might help you.
This is the code for one of them (the others are similar, but with, for example, 'theme-light-purple' replaced by 'theme-light-blue' or 'theme-dark-blue'. 'theme-dark-purple' is the default.):
<button id="switch-light-purple" onclick="setTheme()">Light Purple</button>
<script>
// function to set a given theme/color-scheme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
// function to set theme
function setTheme() {
setTheme('theme-light-purple');
}
// Immediately invoked function to set the theme on initial load
(function () {
if (localStorage.getItem('theme') === 'theme-light-purple') {
setTheme('theme-light-purple');
} else {
setTheme('theme-dark-purple');
}
})();
</script>
<button id="switch-light-purple" onclick="setTheme('theme-light-purple-dark')">Light Purple</button>
<script>
// function to set a given theme/color-scheme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
// Immediately invoked function to set the theme on initial load
(function () {
if (localStorage.getItem('theme') === 'theme-light-purple') {
setTheme('theme-light-purple');
} else {
setTheme('theme-dark-purple');
}
})();
</script>
You don't need to write 2 functions.
This is how you should have your code
function setTheme(themeName = 'theme-light-purple') {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
Here, you are providing a default value to your first parameter in case you want to call the setTheme function without any parameters like this setTheme()
So, if you call setTheme() it automatically means you are calling setTheme('theme-light-purple')
Here's the official documentation for default parameters if you want to go through.
If you want setTheme() to behave differently whether it receives or not an argument (and if I understood what you want to achieve), you need to replace:
// function to set a given theme/color-scheme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
// function to set theme
function setTheme() {
setTheme('theme-light-purple');
}
with:
// function to set a given theme/color-scheme or a default one if none is provided
function setTheme(themeName) {
const chosenTheme = themeName ? themeName : 'theme-light-purple';
localStorage.setItem('theme', chosenTheme);
document.documentElement.className = chosenTheme;
}
[EDIT] Following the comment, I am adding a partial demo; unfortunately the code snippet cannot access window.localStorage therefore the storing to and retrieving from localStorage of the theme cannot be demonstrated here:
/* theme default */
html.theme-default body {
color: #333;
background-color: #efefef;
}
html.theme-default button {
border: 2px solid #333;
border-radius: 3px;
color: #333;
}
html.theme-default p {
border: 1px dashed #333;
color: #333;
}
/* theme red */
html.theme-red body {
color: #300;
background-color: #ffefef;
}
html.theme-red button {
border: 2px solid #c00;
border-radius: 3px;
color: #300;
}
html.theme-red p {
border: 1px dashed #c00;
color: #300;
}
/* theme green */
html.theme-green body {
color: #030;
background-color: #efffef;
}
html.theme-green button {
border: 2px solid #0c0;
border-radius: 3px;
color: #030;
}
html.theme-green p {
border: 1px dashed #0c0;
color: #030;
}
/* theme blue */
html.theme-blue body {
color: #003;
background-color: #efefff;
}
html.theme-blue button {
border: 2px solid #00c;
border-radius: 3px;
color: #003;
}
html.theme-blue p {
border: 1px dashed #00c;
color: #003;
}
<body>
<button id="switch-default" onclick="setTheme()">default (grey)</button>
<button id="switch-red" onclick="setTheme('theme-red')">red</button>
<button id="switch-green" onclick="setTheme('theme-green')">green</button>
<button id="switch-blue" onclick="setTheme('theme-blue')">blue</button>
<p>Lorem ipsum dolor sit consecutor amet</p>
<script>
function setTheme(themeName) {
const chosenTheme = themeName ? themeName : 'theme-default';
// window.localStorage.setItem('theme', chosenTheme); // this line is commented as the code snippet has no access to localStorage
document.documentElement.className = chosenTheme;
}
// Immediately invoked function to set the theme on initial load
(function() {
/* the following lines are commented as the code snippet has no access to localStorage
const storedTheme = window.localStorage.getItem('theme') || null;
if (storedTheme) {
setTheme(storedTheme);
} else {
setTheme();
}
*/
setTheme() // this line should be deleted in the actual code
})();
</script>
</body>

I have a light/dark theme toggle react component that I'm trying to improve

I have a dark theme toggle switch for my React component that works well, but I know the code could use some work. First I am checking to see what the users' preferences are for light/dark. Then I am setting in local storage what the user has chosen as a preference and then also checking for that value. I'm still learning and would like some feedback.
My requirements:
Needs to check the users' system preference for light/dark
Needs to persistently keep users light/dark preference throughout the site
Add class dark to the body tag in order to active all the CSS changes
Thanks in advance!
import React, { Component } from "react";
class LightDarkToggle extends React.Component {
componentDidMount() {
let bodyClassList = document.body.classList;
let themeSwitch = document.getElementById('switch-style');
//Here we are checking the USERS' device settings for dark theme
if(window.matchMedia('(prefers-color-scheme: dark)').matches) {
bodyClassList.add('dark');
themeSwitch.checked = true;
}
else if(window.matchMedia('(prefers-color-scheme: light)').matches) {
bodyClassList.remove('dark');
themeSwitch.checked = false;
}
//Persisting USERS' theme preference by checking the local storage we set
if(window.localStorage.getItem('theme') === 'dark') {
bodyClassList.add('dark');
themeSwitch.checked = true;
}
else if(window.localStorage.getItem('theme') === 'light') {
bodyClassList.remove('dark');
themeSwitch.checked = false;
}
}
handleClick() {
document.body.classList.toggle('dark');
if(document.getElementById('switch-style').checked === true){
window.localStorage.setItem('theme', 'dark');
}
else if (document.getElementById('switch-style').checked === false){
window.localStorage.setItem('theme', 'light');
}
}
render() {
return (
<div className="toggle-mode">
<div className="icon">
<i className="fa fa-sun-o" aria-hidden="true"></i>
</div>
<div className="toggle-switch">
<label className="switch">
<input type="checkbox" id="switch-style" onClick={this.handleClick} />
<div className="slider round" id="lightDarkToggle"></div>
</label>
</div>
<div className="icon">
<i className="fa fa-moon-o" aria-hidden="true"></i>
</div>
</div>
);
}
}
export default LightDarkToggle;```
You can also use React Context. Context is very easy to use and you don't have to write long code. Please check these official docs https://reactjs.org/docs/context.html , they have also done the same thing but in a React way.
This is a problem I've recently worked on myself. I can provide some guidance, perhaps.
We'll make use of the fact that you can use data-attributes on html.
We can also use CSS variables to make this very easy. You can then define a variable in the data-theme attribute selector on HTML and it will be applied to all children of HTML using that attribute.
You're already using localStorage for saving the currently selected theme, which is good. I don't think there's any improvement to be made there.
Here's a quick example I threw together of the above mentioned ideas put together: https://jsfiddle.net/gc9m8pea/2/
And here's snippet form, though it won't actually function because localStorage isn't permitted here.
function getTheme() {
// Default to Light if key not present.
return window.localStorage.getItem('theme') || 'light';
}
function getOppositeTheme() {
const theme = getTheme();
if (theme === 'light')
return 'dark';
else
return 'light';
}
window.onload = () => {
const button = document.querySelector('#themeToggle');
const html = document.querySelector('html');
if (html)
html.setAttribute('data-theme', getTheme());
button.addEventListener('click', () => {
const newTheme = getOppositeTheme();
html.setAttribute('data-theme', newTheme);
window.localStorage.setItem('theme', newTheme);
});
};
html[data-theme="dark"] {
--bg-color: rgb(40, 40, 40);
--text-color: rgb(220, 220, 220);
}
html[data-theme="light"] {
--bg-color: rgb(255, 255, 255);
--text-color: rgb(0, 0, 0);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
<html>
<body>
<button id="themeToggle">
Toggle Theme
</button>
<hr>
Hello world
</body>
</html>
This should be fairly easy to use inside React, as the concepts themselves are not React-specific.
Hope this helps!

Combine Mobile and Screen function

Trying to get this function to work properly. The toggleNav works on it's own. I want to apply different Open/close functionality based on screen size. the navigation opens and closes based on mouse events, so the code needs to continuously run at each screen size.
let opened = false; // set the nav as closed by default
if ($(window).width() > 720) {
function toggleNav() {
if(!opened) { // if opened is false (ie nav is closed), open the nav
openNav()
} else { // else, if opened is ture (ie nav is open), close the nav
closeNav();
}
opened = !opened; // negate boolean to get opposite (t to f, and f to t)
}
}else{
function toggleNav2() {
if(!opened) {
openNav2()
} else {
closeNav2();
}
opened = !opened;
}
}
function openNav() {
$('#myTopnav').addClass('openHeight').removeClass('closeHeight');
$('#main').addClass('openMain').removeClass('closeMain');
}
function closeNav() {
$('#myTopnav').removeClass('openHeight').addClass('closeHeight');
$('#main').removeClass('openMain').addClass('closeMain');
}
function openNav2() {
$('#main').addClass('openMain').removeClass('closeMain');
}
function closeNav2() {
$('#main').removeClass('openMain').addClass('closeMain');
}
I'm guessing that you're asking how to make this work in the event that the user resizes their view.
I would recommend rearranging your styles so that there is one master class that controls this feature, on a single parent element (e.g.: body), which you can then toggle on and off via javascript. In this way, all style changes (including those caused by user view size changes) are handled by CSS and javascript only has to manage a single element.
You don't provide enough of your code to rework what you're doing but, as an example (click "expand snippet" and resize your browser window as needed to experiment with the width):
$('button').on('click', e => {
$('body').toggleClass('off');
});
.top:after {
display:block;
content:"on";
color: #080;
}
.bottom:after {
display:block;
content:"on";
color: #080;
}
/* overrides for "off" state */
.off .top:after {
content:"off";
color: #800;
}
/* only override css if view width > 720px */
#media (min-width:721px) {
.off .bottom:after {
content:"off";
color: #800;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button">toggle</button>
<div class="top"></div>
<div class="bottom"></div>

How to override css prefers-color-scheme setting

I am implementing a dark mode, as macOS, Windows and iOS have all introduced dark modes.
There is a native option for Safari, Chrome, and Firefox, using the following CSS media rule:
#media (prefers-color-scheme: dark) {
body {
color:#fff;
background:#333333
}
This will automatically identify systems that are set to dark modes, and apply the enclosed CSS rules.
However; even though users may have their system set to dark mode, it may be the case that they prefer the light or default theme of a specific website. There is also the case of Microsoft Edge users which does not (yet) support #media (prefers-color-scheme. For the best user experience, I want to ensure that these users can toggle between dark and default modes for those cases.
Is there a method that this can be performed, possibly with HTML 5 or JavaScript? I'd include the code I have tried, but I haven't been able to find any information on implementing this whatsoever!
I have determined an appropriate solution, it is as follows:
CSS will use variables and themes:
// root/default variables
:root {
--font-color: #000;
--link-color:#1C75B9;
--link-white-color:#fff;
--bg-color: rgb(243,243,243);
}
//dark theme
[data-theme="dark"] {
--font-color: #c1bfbd;
--link-color:#0a86da;
--link-white-color:#c1bfbd;
--bg-color: #333;
}
The variables are then called where necessary, for example:
//the redundancy is for backwards compatibility with browsers that do not support CSS variables.
body
{
color:#000;
color:var(--font-color);
background:rgb(243,243,243);
background:var(--bg-color);
}
JavaScript is used to identify which theme the user has set, or if they have over-ridden their OS theme, as well as to toggle between the two, this is included in the header prior to the output of the html <body>...</body>:
//determines if the user has a set theme
function detectColorScheme(){
var theme="light"; //default to light
//local storage is used to override OS theme settings
if(localStorage.getItem("theme")){
if(localStorage.getItem("theme") == "dark"){
var theme = "dark";
}
} else if(!window.matchMedia) {
//matchMedia method not supported
return false;
} else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
//OS theme setting detected as dark
var theme = "dark";
}
//dark theme preferred, set document with a `data-theme` attribute
if (theme=="dark") {
document.documentElement.setAttribute("data-theme", "dark");
}
}
detectColorScheme();
This javascript is used to toggle between the settings, it does not need to be included in the header of the page, but can be included wherever
//identify the toggle switch HTML element
const toggleSwitch = document.querySelector('#theme-switch input[type="checkbox"]');
//function that changes the theme, and sets a localStorage variable to track the theme between page loads
function switchTheme(e) {
if (e.target.checked) {
localStorage.setItem('theme', 'dark');
document.documentElement.setAttribute('data-theme', 'dark');
toggleSwitch.checked = true;
} else {
localStorage.setItem('theme', 'light');
document.documentElement.setAttribute('data-theme', 'light');
toggleSwitch.checked = false;
}
}
//listener for changing themes
toggleSwitch.addEventListener('change', switchTheme, false);
//pre-check the dark-theme checkbox if dark-theme is set
if (document.documentElement.getAttribute("data-theme") == "dark"){
toggleSwitch.checked = true;
}
finally, the HTML checkbox to toggle between themes:
<label id="theme-switch" class="theme-switch" for="checkbox_theme">
<input type="checkbox" id="checkbox_theme">
</label>
Through the use of CSS variables and JavaScript, we can automatically determine the users theme, apply it, and allow the user to over-ride it as well. [As of the current time of writing this (2019/06/10), only Firefox and Safari support the automatic theme detection]
Not sure, why all answers are so complicated.
Use CSS variables, set a default value, and an opposite value in a media query, as usual. Also set the values in two classes. Implement a toggle that toggles these classes when clicked.
By default, automatic light/dark mode is used based on the system color scheme. Using the toggle switches to manual light/dark mode. It returns to automatic light/dark mode after refreshing the page (or removing the class from the html element).
// toggle to switch classes between .light and .dark
// if no class is present (initial state), then assume current state based on system color scheme
// if system color scheme is not supported, then assume current state is light
function toggleDarkMode() {
if (document.documentElement.classList.contains("light")) {
document.documentElement.classList.remove("light")
document.documentElement.classList.add("dark")
} else if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark")
document.documentElement.classList.add("light")
} else {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add("dark")
} else {
document.documentElement.classList.add("light")
}
}
}
/* automatic/manual light mode */
:root, :root.light {
--some-value: black;
--some-other-value: white;
}
/* automatic dark mode */
/* ❗️ keep the rules in sync with the manual dark mode below! */
#media (prefers-color-scheme: dark) {
:root {
--some-value: white;
--some-other-value: black;
}
}
/* manual dark mode
/* ❗️ keep the rules in sync with the automatic dark mode above! */
:root.dark {
--some-value: white;
--some-other-value: black;
}
/* use the variables */
body {
color: var(--some-value);
background-color: var(--some-other-value);
}
<button onClick="toggleDarkMode()">Toggle</button>
<h1>Hello world!</h1>
You can use my custom element <dark-mode-toggle> that initially adheres to the user's prefers-color-scheme setting, but that also allows the user to (permanently or temporarily) override it. The toggle works both with separate CSS files or with classes that are toggled. The README has examples for both approaches.
Here's an answer that respects the default prefers-color-scheme, and only then lets you toggle via localStorage. This assumes CSS does it faster than JS plus people will use the default scheme even without JS.
I don't like having to declare a default style and then re-declaring it as a standalone class called, but it's unavoidable. I at least used :root to avoid duplicated values.
Note this forum seems to block localStorage so you have to try the code somewhere else.
var theme, prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
if (prefersDarkScheme.matches)
theme = document.body.classList.contains("light-mode") ? "light" : "dark";
else
theme = document.body.classList.contains("dark-mode") ? "dark" : "light";
localStorage.setItem("theme", theme);
function toggle() {
var currentTheme = localStorage.getItem("theme");
if (currentTheme == "dark")
document.body.classList.toggle("light-mode");
else if (currentTheme == "light")
document.body.classList.toggle("dark-mode");
}
:root {
--text-for-light: black;
--bkg-for-light: white;
--link-for-light: green;
--text-for-dark: white;
--bkg-for-dark: black;
--link-for-dark: DeepSkyBlue;
}
body {color: var(--text-for-light); background-color: var(--bkg-for-light);}
a {color: var(--link-for-light);}
.dark-mode {color: var(--text-for-dark); background-color: var(--bkg-for-dark);}
.dark-mode a {color: var(--link-for-dark);}
.light-mode {color: var(--text-for-light); background-color: var(--bkg-for-light);}
.light-mode a {color: var(--link-for-light);}
#media (prefers-color-scheme: dark) {
body {color: var(--text-for-dark); background-color: var(--bkg-for-dark);}
a {color: var(--link-for-dark);}
}
<button onclick="toggle()">Toggle Light/Dark Mode</button>
<p> </p>
Test link
If you want just the auto detect part without the toggle button:
:root {
--text-for-light: black;
--bkg-for-light: white;
--link-for-light: green;
--text-for-dark: white;
--bkg-for-dark: black;
--link-for-dark: DeepSkyBlue;
}
body {color: var(--text-for-light); background-color: var(--bkg-for-light);}
a {color: var(--link-for-light);}
#media (prefers-color-scheme: dark) {
body {color: var(--text-for-dark); background-color: var(--bkg-for-dark);}
a {color: var(--link-for-dark);}
}
Test link
TL;DR
index.html
<!DOCTYPE html>
<html>
<head>
<meta name="color-scheme" content="light dark">
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h1>Hello world</h1>
<button id="toggle">Toggle</button>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
style.css
.dark-mode {
background-color: black;
color: white;
}
.light-mode {
background-color: white;
color: black;
}
#media (prefers-color-scheme: dark) {
body {
background-color: black;
color: white;
}
}
script.js
/**
* Adopt:
* the theme from the system preferences; or
* the previously stored mode from the `localStorage`
*/
var initialMode = "light";
var prefersColorSchemeDark = window.matchMedia( "(prefers-color-scheme: dark)" );
if ( prefersColorSchemeDark.matches ) {
initialMode = "dark";
}
if( localStorage.getItem("initialMode") == null ) {
localStorage.setItem("initialMode", initialMode);
}
if( localStorage.getItem("currentMode") == null ) {
localStorage.setItem("currentMode", initialMode);
} else {
let currentMode = localStorage.getItem("currentMode");
if ( currentMode == "dark" && currentMode != initialMode ) {
document.body.classList.add("dark-mode");
} else if ( currentMode == "light" && currentMode != initialMode ) {
document.body.classList.add("light-mode");
}
}
/**
* Process the toggle then store to `localStorage`
*/
document.getElementById('toggle').addEventListener("click", function() {
var initialMode = localStorage.getItem("initialMode");
let currentMode = localStorage.getItem("currentMode");
if ( currentMode == "dark" && currentMode == initialMode ) {
document.body.classList.add("light-mode");
localStorage.setItem("currentMode", "light");
} else if ( currentMode == "light" && currentMode == initialMode ) {
document.body.classList.add("dark-mode");
localStorage.setItem("currentMode", "dark");
} else if ( currentMode != initialMode ) {
document.body.removeAttribute("class");
if( currentMode == "dark" ) {
localStorage.setItem("currentMode", "light");
} else {
localStorage.setItem("currentMode", "dark");
}
}
},
false);
Details
This solution assumes that:
Whatever was set on the system preferences (dark/light mode), that will be acknowledged as the initial mode
From the initial mode, the end-user then can toggle manually either dark mode or light mode
If the system does not have a dark mode feature, the light mode theme will be used
Whatever the theme (dark/light mode) that the end-user manually set previously, that will be the new initial mode on the next page reload/refresh
Took the solution provided by #JimmyBanks and 1) turned the checkbox into a toggling text button, and 2) added automatic theme switching on OS theme change.
CSS is unchanged, with light themes stored in the :root and dark themes stored under [data-theme="dark"]:
:root {
--color_01: #000;
--color_02: #fff;
--color_03: #888;
}
[data-theme="dark"] {
--color_01: #fff;
--color_02: #000;
--color_03: #777;
}
The <head> JS has some edits, including a few omissions and the move of the data-theme statement to the subsequent JS block:
var theme = 'light';
if (localStorage.getItem('theme')) {
if (localStorage.getItem('theme') === 'dark') {
theme = 'dark';
}
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
theme = 'dark';
}
And here is the edit to the second block of JS, plus related HTML. theme_switch toggles the theme, while theme_OS automatically updates the site's theme with changes to the OS theme.
var theme;
function theme_apply() {
'use strict';
if (theme === 'light') {
document.getElementById('theme_readout').innerHTML = 'Dark';
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
} else {
document.getElementById('theme_readout').innerHTML = 'Light';
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
}
}
theme_apply();
function theme_switch() {
'use strict';
if (theme === 'light') {
theme = 'dark';
} else {
theme = 'light';
}
theme_apply();
}
var theme_OS = window.matchMedia('(prefers-color-scheme: light)');
theme_OS.addEventListener('change', function (e) {
'use strict';
if (e.matches) {
theme = 'light';
} else {
theme = 'dark';
}
theme_apply();
});
<a onclick="theme_switch()">Theme: <span id="theme_readout"></span></a>
Please let me know if you have any suggestions for improvement!
None of the above suited me. I decided to approach the problem from a different perspective. Year is 2021.
The following offers:
System preferences respect.
System preferences overwrite.
Scrollbars color scheme respect.
Universal browser support. (IE end of life, August 17th 2021 🥳✌️🎉)
When you take a look at the MDN Web Docs page for prefers-color-scheme you can read the following:
The prefers-color-scheme CSS media feature is used to detect if the user has requested a light or dark color theme.
[...]
light Indicates that user has notified that they prefer an interface that has a light theme, or has not expressed an active preference.
So for any browsers, by default, the prefers-color-scheme is either set to light or isn't supported.
One of the problem I had with the accepted answer was that the changes were not affecting the scrollbar color. This can be handle using the color-scheme CSS property coupled to the :root pseudo element.
The other problem I had was that, If a user was to change the system settings to light or dark, the website wouldn't be affeted by it and would generate miss-matches between both styles. We can fix that behaviour by coupling window.matchMedia( '(prefers-color-scheme: light)' ) to a onchange event listener.
Here is the final script.
(() => {
var e = document.getElementById("tglScheme");
window.matchMedia("(prefers-color-scheme: dark)").matches
? (document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
document.body.classList.add("dark"),
e && (e.checked = !0),
window.localStorage.getItem("scheme") &&
(document.getElementById("scheme").remove(), document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'), document.body.classList.remove("dark"), e && (e.checked = !1)),
e &&
e.addEventListener("click", () => {
e.checked
? (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
document.body.classList.add("dark"),
localStorage.removeItem("scheme"))
: (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
document.body.classList.remove("dark"),
localStorage.setItem("scheme", 1));
}))
: (document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
e && (e.checked = !1),
window.localStorage.getItem("scheme") &&
(document.getElementById("scheme").remove(), document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'), document.body.classList.add("dark"), e && (e.checked = !0)),
e &&
e.addEventListener("click", () => {
e.checked
? (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
document.body.classList.add("dark"),
localStorage.setItem("scheme", 1))
: (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
document.body.classList.remove("dark"),
localStorage.removeItem("scheme"));
}));
})(),
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
location.reload(), localStorage.removeItem("scheme");
});
For the CSS side, we use the default variable custom property values fallback with the dark color in first position. We can define all the necessary dark colors via the :root element.
:root body.dark {
--app-bg-dark: #131313;
--app-tx-dark: #f8f9fa;
}
body{
background-color: var( --app-bg-dark, white );
color: var( --app-tx-dark, black );
}
/* if dark mode isn't set, fall back to light. */
And for the html, a simple checkbox <input id="tglScheme" type="checkbox">.
Finally here is the Codepen https://codepen.io/amarinediary/full/yLgppWW.
⚠️️ Codepen overwrites location.reload() so you won't be abble to test the live update on system change. Don't hesitate to try it on your localhost.
An alternative solution I found using the blog mybyways which is not mentioned anywhere else but works for me. This is useful only when html uses the prefers-color-scheme css media classes.
Unlike other answers, it uses the stylesheets' rules to append the class (as opposed to adding or removing "dark" or "light" from classList)
By default it takes the style of OS setting and overrides it when toggled. I tried from Google Chrome labs but it didn't work out for me.
function setPreferredColorScheme(mode = "dark") {
console.log("changing")
for (var i = document.styleSheets[0].rules.length - 1; i >= 0; i--) {
rule = document.styleSheets[0].rules[i].media;
if (rule.mediaText.includes("prefers-color-scheme")) {
console.log("includes color scheme")
switch (mode) {
case "light":
console.log("light")
rule.appendMedium("original-prefers-color-scheme");
if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
if (rule.mediaText.includes("dark")) rule.deleteMedium("(prefers-color-scheme: dark)");
break;
case "dark":
console.log("dark")
rule.appendMedium("(prefers-color-scheme: light)");
rule.appendMedium("(prefers-color-scheme: dark)");
if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
break;
default:
console.log("default")
rule.appendMedium("(prefers-color-scheme: dark)");
if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
}
break;
}
}
}
#media (prefers-color-scheme: light) {
:root {
color: pink;
background-color: yellow;
}
}
#media (prefers-color-scheme: dark) {
:root {
color: red;
background-color: blue;
}
}
<body>
<button onClick="setPreferredColorScheme()">
toggle
</button>
</body>
Above is a working example ^
Full source: https://mybyways.com
I just did this today and worked with the dark mode preferences. I think this solution is simpler than all the ones I looked at here.
The toggle darkmode button is optional. Local storage is used, works with iPhone dark mode.
Finally, my research was all the posts here. I worked out an "optimized" solution. See what kind of traction you get. I included the SVG to make things easier.
EDIT: I updated the JS function to check for availability of local storage. I also moved the function variables into the function itself other than the toggle used for real-time setting storage. It is slightly longer this way but more correct and still shorter than most the others.
function checkForLocalStorage () {
try {
localStorage.setItem('test', 1)
localStorage.removeItem('test')
return true
} catch (e) { return false }
}
const hasLocalStorage = checkForLocalStorage()
let isThemeDark = null
function toggleDarkMode () {
const isPreferDark = window.matchMedia('(prefers-color-scheme: dark)').matches
const localPref = hasLocalStorage ? localStorage.getItem('isThemeDark') : null
const hasLocalPref = !!localPref
if (isThemeDark === null && hasLocalPref) isThemeDark = localPref === 'dark'
else if (isThemeDark === null && isPreferDark) isThemeDark = true
else if (isThemeDark === null) isThemeDark = false
else isThemeDark = !isThemeDark
const theme = isThemeDark ? 'dark' : 'light'
if (hasLocalStorage) localStorage.setItem('isThemeDark', theme)
document.body.classList[isThemeDark ? 'add' : 'remove']('dark-mode')
}
toggleDarkMode()
body.dark-mode { background: #222; color: #f2f2f2; }
body.dark-mode #darkModeToggle svg { fill: #fff; }
<a href="#" id="darkModeToggle" onclick="toggleDarkMode()">
<svg width="24px" height="24px"><path d="M12,22 C17.5228475,22 22,17.5228475 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,17.5228475 6.4771525,22 12,22 Z M12,20.5 L12,3.5 C16.6944204,3.5 20.5,7.30557963 20.5,12 C20.5,16.6944204 16.6944204,20.5 12,20.5 Z"/></svg>
</a>
<div>Hello World!</div>
My Solution (3 options in radio inputs: dark, system, light) adaptation of JimmyBanks and Meanderbilt Solution:
its a bit verbose I guess, but I struggled a bit to wrap my head around it
const themeSwitches = document.querySelectorAll('[data-color-theme-toggle]')
function removeColorThemeLocalStorage() {
localStorage.removeItem('color-theme')
}
function saveColorTheme(colorTheme) {
if (colorTheme === 'system') {
removeColorThemeLocalStorage()
return
}
localStorage.setItem('color-theme', colorTheme)
}
function applyColorTheme() {
const localStorageColorTheme = localStorage.getItem('color-theme')
const colorTheme = localStorageColorTheme || null
if (colorTheme) {
document.documentElement.setAttribute('data-color-theme', colorTheme)
}
}
function themeSwitchHandler() {
themeSwitches.forEach(themeSwitch => {
const el = themeSwitch
if (el.value === localStorage.getItem('color-theme')) {
el.checked = true
}
el.addEventListener('change', () => {
if (el.value !== 'system') {
saveColorTheme(el.value)
applyColorTheme(el.value)
} else {
removeColorThemeLocalStorage()
document.documentElement.removeAttribute('data-color-theme')
}
})
})
applyColorTheme()
}
document.addEventListener('DOMContentLoaded', () => {
themeSwitchHandler()
applyColorTheme()
})
html {
--hue-main: 220;
--color-text: hsl(var(--hue-main), 10%, 25%);
--color-text--high-contrast: hsl(var(--hue-main), 10%, 5%);
--color-link: hsl(var(--hue-main), 40%, 30%);
--color-background: hsl(var(--hue-main), 51%, 98.5%);
}
#media (prefers-color-scheme: dark) {
html.no-js {
--color-text: hsl(var(--hue-main), 5%, 60%);
--color-text--high-contrast: hsl(var(--hue-main), 10%, 80%);
--color-link: hsl(var(--hue-main), 60%, 60%);
--color-background: hsl(var(--hue-main), 10%, 12.5%);
}
}
[data-color-theme='dark'] {
--color-text: hsl(var(--hue-main), 5%, 60%);
--color-text--high-contrast: hsl(var(--hue-main), 10%, 80%);
--color-link: hsl(var(--hue-main), 60%, 60%);
--color-background: hsl(var(--hue-main), 10%, 12.5%);
}
<div class="color-scheme-toggle" role="group" title="select a color scheme">
<p>saved setting: <span class="theme-readout">...</span></p>
<input type="radio" name="scheme" id="dark" value="dark" aria-label="dark color scheme"> <label for="dark">dark</label>
<input type="radio" name="scheme" id="system" value="system" aria-label="system color scheme" checked="system"> <label for="system">system</label>
<input type="radio" name="scheme" id="light" value="light" aria-label="light color scheme"> <label for="light">light</label>
</div>
I believe the best way is to natively follow system settings unless user says otherwise.
Create button in your html. And then bind three-position switch to it with js. With saving to browser's LocalStorage.
And, finally, stylize your switch element.
document.addEventListener("DOMContentLoaded", function(event) {
switchTheme('.theme-switch');
});
function switchTheme(selector) {
const switches = document.querySelectorAll(selector);
// let colorTheme = localStorage.getItem('colorTheme') || 'system'; //commented to avoid security issue
let colorTheme = 'system';
function changeState() {
// localStorage.setItem('colorTheme', colorTheme); //commented to avoid security issue
document.documentElement.setAttribute('data-theme', colorTheme);
}
changeState();
switches.forEach(el => {
el.addEventListener('click', () => {
switch (colorTheme) {
case 'dark':
colorTheme = 'light';
break
case 'light':
colorTheme = 'system';
break
default:
colorTheme = 'dark';
}
changeState();
});
});
}
:root:not([data-theme="dark"]) {
--bg: #fff;
}
#media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #000;
}
}
:root[data-theme="dark"] {
/* yep, you'll need to duplicate styles from above */
--bg: #000;
}
body {
background: var(--bg);
}
.theme-switch:after {
content: ': system';
}
:root[data-theme="dark"] .theme-switch:after {
content: ': dark';
}
:root[data-theme="light"] .theme-switch:after {
content: ': light';
}
<button class="theme-switch">Color scheme</button>
I suggest using SCSS. You can make it more simpler.
/* Dark Mode */
#mixin darkMixin {
body {
color: #fff;
background: #000;
}
}
#media (prefers-color-scheme: dark) {
#include darkMixin;
}
.darkMode {
#include darkMixin;
}
.lightMode {
body {
color: #000;
background: #fff;
}
}
And you can toggle/override using JavaScript.
(In this example, I used jQuery to make it look easy)
// dark
$('html').removeClass('lightMode').addClass('darkMode')
// light
$('html').removeClass('darkMode').addClass('lightMode')
If you want to detect, this is the code based on JimmyBanks' one.
function isDarkTheme(){
let theme="light"; //default to light
if (localStorage.getItem("theme")){
if (localStorage.getItem("theme") == "dark")
theme = "dark"
} else if (!window.matchMedia) {
return false
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark"
}
return theme=='dark'
}
To save the current theme, just use localStorage:
localStorage.setItem("theme", 'light')
or
localStorage.setItem("theme", 'dark')
My answer is based on this one, but I have included changes I had to make to get it working, plus I added the ability to persist in local storage.
The important point is that it works with #media (prefers-color-scheme: dark) CSS, and there was no need to create or duplicate additional CSS classes just for this to work. In other words it works with native CSS color scheme.
On the page I first added a sun/moon icon and set them to invisible.
<a href="javascript:toggleColorScheme();">
<span id="icon-sun">🌞</span>
<span id="icon-moon">🌚</span>
</a>
<style>
#icon-sun {
width: 1.5rem;
height: 1.5rem;
display: none;
}
#icon-moon {
width: 1.5rem;
height: 1.5rem;
display: none;
}
</style>
Then, this Javascript which does the bulk of the work.
// https://stackoverflow.com/questions/56300132/how-to-override-css-prefers-color-scheme-setting
// Return the system level color scheme, but if something's in local storage, return that
// Unless the system scheme matches the the stored scheme, in which case... remove from local storage
function getPreferredColorScheme(){
let systemScheme = 'light';
if(window.matchMedia('(prefers-color-scheme: dark)').matches){
systemScheme = 'dark';
}
let chosenScheme = systemScheme;
if(localStorage.getItem("scheme")){
chosenScheme = localStorage.getItem("scheme");
}
if(systemScheme === chosenScheme){
localStorage.removeItem("scheme");
}
return chosenScheme;
}
// Write chosen color scheme to local storage
// Unless the system scheme matches the the stored scheme, in which case... remove from local storage
function savePreferredColorScheme(scheme){
let systemScheme = 'light';
if(window.matchMedia('(prefers-color-scheme: dark)').matches){
systemScheme = 'dark';
}
if(systemScheme === scheme){
localStorage.removeItem("scheme");
}
else {
localStorage.setItem("scheme", scheme);
}
}
// Get the current scheme, and apply the opposite
function toggleColorScheme(){
let newScheme = "light";
let scheme = getPreferredColorScheme();
if (scheme === "light"){
newScheme = "dark";
}
applyPreferredColorScheme(newScheme);
savePreferredColorScheme(newScheme);
}
// Apply the chosen color scheme by traversing stylesheet rules, and applying a medium.
function applyPreferredColorScheme(scheme) {
for (var i = 0; i <= document.styleSheets[0].rules.length-1; i++) {
rule = document.styleSheets[0].rules[i].media;
if (rule && rule.mediaText.includes("prefers-color-scheme")) {
switch (scheme) {
case "light":
rule.appendMedium("original-prefers-color-scheme");
if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
if (rule.mediaText.includes("dark")) rule.deleteMedium("(prefers-color-scheme: dark)");
break;
case "dark":
rule.appendMedium("(prefers-color-scheme: light)");
rule.appendMedium("(prefers-color-scheme: dark)");
if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
break;
default:
rule.appendMedium("(prefers-color-scheme: dark)");
if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
break;
}
}
}
// Change the toggle button to be the opposite of the current scheme
if(scheme === "dark"){
document.getElementById("icon-sun").style.display='inline';
document.getElementById("icon-moon").style.display='none';
}
else {
document.getElementById("icon-moon").style.display='inline';
document.getElementById("icon-sun").style.display='none';
}
}
applyPreferredColorScheme(getPreferredColorScheme());
So on page load, the applyPreferredColorScheme(getPreferredColorScheme()) method runs, which checks the system and local storage and figures out which theme to apply. It also switches between the sun or moon icon depending on the current theme.
When the user clicks the icon to toggle the theme, toggleColorScheme() runs which stores the chosen theme in local storage, but one difference - if the user switches back to the theme that matches their OS, the code simply removes the item from local storage. Trying to keep it as native as possible.
Looks like there's plenty of answers here already, but nothing quite matching my needs. I wanted to be able to:
Use the OS preference if no other preference is set.
If using the OS preference, have the page reflect changes to it instantly.
Allow a query string param to override the OS preference.
If the user explicitly sets a preference, use that and save for future use.
Have the checkbox on the page reflect the theme currently in effect.
Toggle the same checkbox is explicitly set a preference.
Have relatively short but clean and readable code.
My CSS looks a lot like that in other solutions. For completeness this is something like:
:root {
/* Support light and dark, with light preferred if no user preference */
color-scheme: light dark;
/* default light mode */
--bg-color: #f7f7f7;
--text-color: #222430;
/* dark mode overrides */
--dark-bg-color: #222430;
--dark-text-color: #f7f7f7;
}
.dark-theme {
/* Use dark mode overrides when class is applied */
--bg-color: var(--dark-bg-color);
--text-color: var(--dark-text-color);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
There is a checkbox with id 'darkThemeToggle' in the HTML. The below runs on page load. (Written in TypeScript)
function manageTheme() {
const darkThemeCheckbox = document.querySelector("#darkThemeToggle") as HTMLInputElement;
const agentPrefersDarkQuery = matchMedia("(prefers-color-scheme: dark)");
function setTheme(arg?: MediaQueryListEvent | string) {
let chosenTheme = "";
if (typeof arg === "string") {
// If this function is called with a string, then an explict preference has
// been set by the user. Use that theme and save the setting for the future.
chosenTheme = arg;
localStorage.setItem("theme", chosenTheme);
} else {
// Use any saved preference, else check for query param, else any OS preference.
chosenTheme = localStorage.getItem("theme") ||
new URLSearchParams(window.location.search).get("theme") ||
(agentPrefersDarkQuery.matches ? "dark" : "light");
}
if (chosenTheme === "dark") {
document.documentElement.classList.add("dark-theme");
} else {
document.documentElement.classList.remove("dark-theme");
}
// Update the UX to reflect the theme that was ultimately applied.
darkThemeCheckbox.checked = (chosenTheme === "dark");
}
// Whenever the user changes the OS preference, refresh the applied theme.
agentPrefersDarkQuery.onchange = setTheme;
// Note that the 'change' event only fires on user action, (not when set in code), which is
// great, else this might cause an infinite loop with the code setting it in setTheme.
darkThemeCheckbox.addEventListener('change', ev => {
let themeChosen = darkThemeCheckbox.checked ? "dark" : "light";
setTheme(themeChosen);
});
setTheme(); // Run on initial load.
}
document.addEventListener("DOMContentLoaded", manageTheme);

Remove backgroundImage on componentDidMount

I currently have a <Login/> page, and a <Dashboard/>.
The login page has a background of #222, and when you login the Dashboard has a background of whitesmoke
The way I am doing this is having this on the body css:
body {
background-color: #222222;
}
and this in the Dashboard.js:
componentWillMount() {
document.body.style.backgroundColor = "whitesmoke";
}
componentWillUnmount() {
document.body.style.backgroundColor = null;
}
Up to now, this was working. But I now have an Image as my background on the Login page, as seen here:
body {
background-color: #222222;
background: url('../../public/img/bg.png');
background-repeat: repeat;
}
but my Dashboard inherits the background image, even when I put something like this:
componentWillMount() {
document.body.style.backgroundImage = null;
document.body.style.backgroundColor = "whitesmoke";
}
componentWillUnmount() {
document.body.style.backgroundColor = null;
}
How do I get around this?
Thanks
Why not use classes instead?
componentWillMount() {
$('body').addClass('has-background');
}
componentWillUnmount() {
$('body').removeClass('has-background');
}
Also, you may want to abstract those addClass / removeClass and use emits.

Categories