The popular direction in allowing for light/dark theme switching is by using CSS Custom Properties. It is most common to simply create a toggle on the page to select light or dark. I'm interested in adding a layer of complexity that allows the user to select the system preference OR an override (of light or dark). Selecting the system preference would then read light and dark at the @media
level. Unless the user hasn't selected a color-scheme, in which case we'll default to some scheme (let's say light).
Assuming the overrides are set by applying attributes to document.documentElement
.
html[theme="light"]
should always provide the light theme even if the user's system is set to dark or not set.html[theme="dark"]
should always provide the dark theme, even if the user's system is set to light or not set.html[theme="none"]
should look at the system preferences and determine...
@media (prefers-color-scheme: dark)
should provide the dark theme.@media (prefers-color-scheme: light)
should provide the light theme.@media (prefers-color-scheme: no-preference)
should provide the default, light theme.The challenge here is that I want to see if I can write the variables for the light/dark themes once as it seems every solution I can think of requires a set of variables to be written in two rulesets.
Looking for a native CSS solution here, as I could do this with build tools; SASS @mixin
code below.
@mixin dark-theme() {
--bg-color: black;
}
:root {
--bg-color: white;
}
html[theme="dark"] {
@include dark-theme(); /* 1st occurrence */
}
@media (prefers-color-scheme: dark) {
html:not([theme="light"]) {
@include dark-theme(); /* 2nd occurrence */
}
}
body {
background-color: var(--bg-color);
}
I'm expecting some sort of combination using :not([theme])
and @media not all and (prefers-color-scheme)
but I've tried a few combinations and have gotten close but not 100% of the logic above. I've also tried switching the specific theme for the fallback (setting the dark theme at the :root
and then attempting to generalize in the @media
rules to apply the light theme) but that didn't seem to work either.
Ah, no JavaScript solutions either please. Again, looking to see if native CSS is possible.
Coming back four years later now that CSS has introduced light-dark()
we can define both light and dark once within the new function and then change the application of those variables with color-scheme
.
body {
color-scheme: light;
}
body[data-theme="dark"] {
color-scheme: dark; /* 1st occurrence */
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
color-scheme: dark; /* 2nd occurrence */
}
}
body {
background-color: light-dark(white, black);
}
I've opted to use body
since the content presentation is what we are affecting and updated to use data
attributes to trigger instead of non-standard ones.