Closed Baptistou closed 3 years ago
Thanks for the suggestion @Baptistou.
I thought about using prefers-color-scheme
when implementing dark mode but decided against it. prefers-color-scheme
is primarily built for use in CSS media queries. I'd want to provide the user an option to control the preference on BookStack, non-dependant on browser/OS setting, so straight away that means we can't use the media query directly. We could detect the prefers-color-scheme
state with JavaScript and set the controlling parent class depending on value but now we're adding mixed front-end and back-end logic for this control which means things are getting more complex. Upon that we would have to provide some level of 'auto' control as suggested so now we're needing some more complex user-control than just the toggle-button(s) we have now.
To me I didn't deem the use of the prefers-color-scheme
worth implementing. If the option was provided by the browser as a header, as the language is, it'd be a bit more open to it.
And what about delegating the light/dark mode implementation to client side ?
I see in the code that the mode button is sending a POST request to server /settings/users/toggle-dark-mode
.
This is a cost that could be avoided by handling the mode change directly in client side.
You even don't need JavaScript at all, the following is a working example with pure CSS :
<body>
<input type="radio" id="light-mode" name="mode">
<label for="light-mode">Light Mode</label>
<input type="radio" id="dark-mode" name="mode">
<label for="dark-mode">Dark Mode</label>
<main>
<p>Hello World !</p>
</main>
</body>
@media (prefers-color-scheme: light){
#light-mode:indeterminate, #light-mode:indeterminate+label {
display: none
}
main {
color: #444;
background-color: #F2F2F2;
}
#dark-mode:checked~main {
color: #AAA;
background-color: #111;
}
}
@media (prefers-color-scheme: dark){
#dark-mode:indeterminate, #dark-mode:indeterminate+label {
display: none
}
main {
color: #AAA;
background-color: #111;
}
#light-mode:checked~main {
color: #444;
background-color: #F2F2F2;
}
}
#light-mode:checked, #light-mode:checked+label,
#dark-mode:checked, #dark-mode:checked+label {
display: none
}
You even don't need JavaScript at all, the following is a working example with pure CSS
Sure, But you're already having to do hacky workarounds, duplicating styles and abusing the intention of the media query. Additionally you'd still need to store the preference which, yeah, could be done with JS localStorage but that won't be cross-device.
Currently we control dark mode via a single CSS class on the html. The easiest way to achieve auto based on prefers-color-scheme
would be to have some JavaScript to query the CSS media state than add that class. As said above though, we'd need to re-think the controls to be, what I think would be, less intuitive. Again, I just didn't see prefers-color-scheme
being something worth implementing. Especially as, In my opinion, the browser implementation of that preference is half-baked.
Sure, But you're already having to do hacky workarounds, duplicating styles and abusing the intention of the media query.
Yes sure, JavaScript is the easiest way.
Additionally you'd still need to store the preference which, yeah, could be done with JS localStorage but that won't be cross-device.
Maybe we could use Cookie via JS to save the preference. This has the benefit to work cross-device and on public access. Having to reset the mode each time you arrive at home page or when you log out is quite annoying.
Again, I just didn't see prefers-color-scheme being something worth implementing.
Of course, this is not a big deal, this feature is not a priority.
Maybe we could use Cookie via JS to save the preference. This has the benefit to work cross-device and on public access.
Cookies themselves would not be a cross-device solution unless the browser happens to sync them.
Of course, this is not a big deal, this feature is not a priority.
Cool, I'll therefore going to close this off due to the reasons provided in my previous messages.
For those interested in a little dirty tinkering:
It adds automatic dark-mode capability in most browsers. BUT the manual selection of the user doesn't work (switches will be removed) and it doesn't work on the settings page itself.
Add this to the custom header: (Don't forget to change https://YOURDOMAIN.XYZ to your actual domain)
<script>
// ** automatic dark-mode
var root = document.getElementsByTagName( 'html' )[0]; //Get (first) html element
//on page-ready
document.addEventListener('DOMContentLoaded', function() {
//switch to mode on page-load
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
root.classList.add('dark-mode');
} else {
root.classList.remove('dark-mode');
}
//hide theme-selector switches
//Drop-Down menu
var drop_menu = document.getElementsByClassName('dropdown-menu')[0];
drop_menu.removeChild(drop_menu.lastElementChild); //mode selector
drop_menu.removeChild(drop_menu.lastChild.previousElementSibling); //hr
//side-menu
var side_menu = document.querySelector('form[action="https://YOURDOMAIN.XYZ/settings/users/toggle-dark-mode"]'); //mode selector
if (side_menu !== null) {
side_menu.remove();
}
}, false);
//on mode-switch event from OS
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (e.matches) {
root.classList.add('dark-mode');
} else {
root.classList.remove('dark-mode');
}
});
</script>
2024 edition :
<script>
// ** automatic dark-mode
var root = document.querySelector('html');
document.addEventListener('DOMContentLoaded', function() {
//switch to mode on page-load
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
root.classList.add('dark-mode');
} else {
root.classList.remove('dark-mode');
}
// Hide theme-selector switches
// Drop-Down menu
var drop_menu = document.getElementsByClassName('dropdown-menu')[0];
drop_menu.removeChild(drop_menu.lastElementChild.previousElementSibling); //mode selector
drop_menu.removeChild(drop_menu.lastElementChild.previousElementSibling); //
// Top bar
document.querySelectorAll('form[action*=\\/preferences\\/toggle-dark-mode]').forEach(e => e.setAttribute("style", "visibility: hidden !important;"))
}, false);
// On mode-switch event from OS
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (e.matches) {
root.classList.add('dark-mode');
} else {
root.classList.remove('dark-mode');
}
});
</script>
Describe the feature you'd like Set light/dark mode by default according to prefers-color-scheme. Moreover, if an .env variable is created (#2081), provide 3 values :
light
--> use light modedark
--> use dark modeauto
--> mode based on prefers-color-scheme, set by defaultDescribe the benefits this feature would bring to BookStack users Improve UX in the same way as language auto detection from user agent.