-
Notifications
You must be signed in to change notification settings - Fork 847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Override system light/dark mode per user profile #1033
Conversation
I wonder if it makes sense to display this in the footer instead, and store this setting as a cookie or localStorage instead of in the database? I believe there are quite a few people who only read Lobsters and don't have an account – as is common for a lot of discussion forums – but they won't be able to change the colour scheme. And due to the invite system you can't really tell them to "just create an account" either. This will also allow people to set preferences per-device; e.g. light on desktop and dark on mobile. I don't even think it's all that much work? |
Yeah, per-session would also be pretty easy to do. |
If you decide to go the JS route, anyone is welcome to use or derive from the dark-mode code I have on my blog for Lobsters (or any other project). I've been meaning to write a small post about it, but it hasn't been a priority lately. // systemDarkMode returns true if system-wide dark mode is enabled.
function systemDarkMode() {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
// getDarkMode returns true if an override value is in localStorage or
// system-wide dark mode is enabled.
function getDarkMode() {
const storedValue = localStorage.getItem('darkMode');
if (storedValue !== undefined) {
return storedValue !== 'false';
}
return systemDarkMode();
}
// toggleDarkMode toggles if dark mode is enabled and saves it in local storage.
function toggleDarkMode() {
const darkMode = getDarkMode();
localStorage.setItem(
'darkMode',
darkMode ? 'false' : 'true',
);
updateDarkMode();
}
// updateDarkMode sets the proper classes on the body based on the current state
function updateDarkMode() {
if (getDarkMode()) {
document.body.classList.remove('light-mode');
document.body.classList.add('dark-mode');
} else {
document.body.classList.add('light-mode');
document.body.classList.remove('dark-mode');
}
// We need a way to show/hide the reset button
if (!localStorage.getItem('darkMode')) {
document.body.classList.remove('dark-mode-value-set');
} else {
document.body.classList.add('dark-mode-value-set');
}
}
// clearDarkMode removes the set value from localStorage and falls back to
// whatever the OS says.
function clearDarkMode() {
localStorage.removeItem('darkMode');
updateDarkMode();
}
// Ensure that if the value changes in another tab, all of them will update.
window.addEventListener('storage', updateDarkMode);
updateDarkMode();
let element = document.getElementById('dark-mode-toggle');
element.style.display = 'block'; The downside of going that route is that for people who want it to sync across all devices, it won't because there's nothing saved server side. This works great for a static blog, it might not be quite as convenient for a site with accounts. It also means that people who have JS disabled will not be able to change the override, though I'd imagine that's a relatively small group of people. |
We don't serve any js unless people log in, and I don't want to add a lot of complexity to this for people who don't want to configure their browser. |
The thing is that browsers don't really allow selecting this per-website AFAIK, or at least not easily. But I don't really care much myself; it's just a thought. |
You can accomplish a dark/light theme 'toggle' with pure CSS, no javascript, using the 'Checkbox Hack': Essentially you put a hidden checkbox at top level: <body><input type="checkbox" id="darkModeCheckbox" style="display: none;"></body> Somewhere else, at the same depth top level, you can put text or icons under <label for="darkModeCheckbox" id="darkModeDarkLabel">switch to dark</label>
<label for="darkModeCheckbox" id="darkModeLightLabel">switch to light</label> Then you only show one of the labels depending on whether it's currently checked or not: #darkModeLightLabel { display: none; }
#darkModeCheckbox:checked ~ #darkModeLightLabel { display: inline; }
#darkModeCheckbox:checked ~ #darkModeDarkLabel { display: none; } So by default, the checkbox is unchecked and the words You can change the labe HTML to whatever you want, two images, maybe a light bulb lit/unlit or any other 'toggle' type icon you want to use. Now that you can toggle this hidden checkbox on and off, you can now style the site based on whether the checkbox is checked. #darkModeCheckbox:checked ~ any other css here { whatever: "you want"; } Problem is, there is no 'stickyness' of this setting without JavaScript. So every time you visited a different page, it would revert back to it's default. Also it won't auto detect based on dark mode preferences either, since it's all gated behind the checkbox hack. The only way around that would be introducing a tiny little javascript snippet to persist the checkbox state and auto set that state based on the persisted setting, falling back to setting it based on detected CSS prefs (which CAN be done with JS). |
Hi, I did #1026. My wish list would be:
A three-option setting defaulting to automatic and stored in a cookie would fit the bill. |
<%= light %> | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm rusty on media queries. Is it be possible to do something like:
@media (prefers-color-scheme: dark), html.color-scheme-dark {
So that we don't have to move these into variables and repeat them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately not; media queries and selectors do not exist at the same level & cannot be combined.
I could add sass
to the sprockets pipeline, which would let us write something sensible and have it compiled into valid CSS - but I didn't want to add a dependency without consulting first, and there was already a .css.erb
files (indicating to me a preference for doing it that way)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's unfortunate. I'd rather not add sass, correct. The asset pipeline is unreliable enough.
I left a comment noting where we may be able to simplify this. Could you add tests that unset produces the |
<html lang="en"> | ||
<html | ||
lang="en" | ||
class='color-scheme-<%= @user&.prefers_color_scheme || 'system' %>' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is almost at the amount of complexity where I'd want to stick it into a helper. If it gets any more complexity, please do so.
Anything still outstanding on this PR? |
I'm trying to sucker @kconner into taking a look to see if they can spot a way out of repeating the CSS, but more likely I'm just working up to biting the bullet on that. |
@pushcx Sorry about letting your email get repeatedly buried. I don't see an obvious way to DRY this, but my working knowledge of CSS 3 is largely happenstance, and there might be a way. |
Thank you for letting me sucker you into taking a look. I will step up my work on accepting the inevitable. |
I have accepted that repetition is the only way to implement this, given the constraints. Thank you again for contributing this, and for your patience with my emotional process. I spent a while tinkering. There's really no way for the server to change something in the HTML that a media query can pick up, CSS can't express an |
I know this is not techsupport, but after disabling the setting (from system to light), the front-page hasn't updated entirely: Comment pages are fine, except for the hats symbol is also off: A logout gives me the dark site, logging back in gives me the screenshot above. (Does not help). I'm on Firefox on Android (11), but I do have battery saving (doze) mode on, which enables the dark mode system wide. Can't turn off just dark mode and still have battery saving... |
No apology needed, this is the best place to report an issue with this PR. @DanielHeath Can you take a look? At a glance I'd guess there's a color or two that didn't survive getting moved? |
Looking. Apparently the small-screen color scheme was defined separately and uses hard-coded colors. |
Fairly simplistic approach - moved the css variables into a .css.erb file and wrote the dark mode settings out twice (once for system, once for the override.
You could avoid sending 20 lines of CSS twice with some JS, but that seems like substantially more overhead.