Blog post title
Blog post content
Light & Dark Modes in Modern CSS
Building Light & Dark Modes in Modern CSS
1οΈβ£ This isn't a fully new talk.
(I worried a little about this, but let myself be convinced to give it again.)
2οΈβ£ Progress in CSS is lightning fast these last few years!
3οΈβ£ This is a talk where almost all of it is usable now.
For those who find this to be old hat, take a nap π΄
an old hand π
(I built websites as a teen from 1999)
and a not-so-newbie π€―
(I switched career into
web dev in 2022,
and began working for pirateship.com)
There'll be some abrupt changes between dark and light mode.
If you suffer from migraines, epilepsy, or strong astigmatism,
I'm really sorry! π
My goal: Make using the web more comfortable for everyone.
You may have spotted a little smiley βοΈ let's test it...
We all love a good polarising argument!
There are physical reasons to prefer one over the other.
And you're also allowed to prefer light or dark themed websites, even for no good or logical reason!
Your eyes, your choice! π
Pupils constrict when coming across a bright page.
Light mode has too much contrast with dark surroundings.
Floaters may be more noticeable, distorting or blocking vision.
Pupils constrict when coming across a bright page!
It's easier to focus when your pupil is smaller.
This is why as you age, you may need brighter light to read.
Astigmatism makes light text on dark backgrounds hard to read.
Astigmatism makes light text on dark backgrounds hard to read.
This upsets people enough to make lots of annoyed simulations:
Both!
Both.
Both is good.
*nods*
No styles, no problem!
However...
Building this into a mature site or design system may be tricky.
Harder still if everything already has specific colours assigned.
Keep it in mind for the next time that you start a new project π±
color-scheme available across browsers since Feb 2022
This one definitely! For the rest, "need" is a strong word...
We'll also look at these helpful features:
These are Baseline statuses.
Once a web feature is implemented across all major browsers, it is labelled Newly available. Then the clock starts ticking.
After 30 months (2.5 years) it is marked Widely available.
This is not related to its global usage as seen on caniuse.com.
A coarse measure of availability, simpler than a percentage.
Blog post content
Blog post content
color-schemeYou don't even need CSS for this!
<head> <meta name="color-scheme" content="light dark"> </head>
:root /* or html */ {
color-scheme: light dark;
}
color-scheme DemoHellooooooo!
You can apply color-scheme to all elements.
But they need to be assigned both a color and background-color.
How are you?
Could be worse!
It's lovely! Storm tomorrow though.
prefers-color-schemeThis follows OS preference,
not color-scheme property.
How are you?
Yeah good, you?
Could be worse!
Nice weather, innit?
How are you?
Yeah good, you?
Could be worse!
Nice weather, innit?
It's lovely! Storm tomorrow.
Ooh I love a good storm!
color-mix() Palettecontrast-color() Arrival!Look at that, it's finally arrived across browsers!
This takes a colour, and returns which has the best contrast against it, black or white.
"Best" contrast here is a little tricky, so avoid mid tones.
light-dark()Instead of all that mixing we can just be specific π
Choose exact colours for dark and light mode.
But, only colors.
...for now π
light-dark()
/* image url values */ light-dark( url("light-icon.png"), url("dark-icon.png") ); /* linear-gradient values */ light-dark( linear-gradient(135deg, ghostwhite 20%, tomato), linear-gradient(45deg, darkslategray 20%, gold) );
if(), color-scheme()
#element {
color-scheme: light dark;
font-weight: if(color-scheme(dark): 300; else: 400);
}
We worry about making a UI high-contrast enough.
But a UI can have too much contrast, too.
Migraineurs can even suffer pain if the contrast is too high π£
Neo-Brutalistic web design has become an issue for some of us.
A study tested sufferers for their aversion to contrast based on the movement of a grating pattern.
I found it interesting that "Drifting" was the worst.
Stark, monochrome websites look cool. But they may also have similarities to a black and white grating pattern.
...And as we scroll, they drift.
Warning! This may be uncomfortable.
If you get migraines or have epilepsy please look down.
(Safe now!)
/* increase */
@media (prefers-contrast: more) {
:root {
background-color: Canvas;
color: CanvasText;
}
}
/* decrease */
@media (prefers-contrast: less) {
:root {
filter: contrast(70%);
}
}
Neat! But how does a user specify low contrast in their OS?
Why in Windows do forced colors = prefers-contrast: more?
Some people like to keep their OS UI in light mode,
and view dark mode web pages, or vice-versa.
(Psst, browser vendors - 'prefers' toggles in toolbars, please π)
Oh John, some days I would love to code less!
To allow true choice, we currently need to roll our own switches.
When relying on prefers-color-scheme, this is a pain.
We have to override it with JavaScript and classes, and/or
double up the declaration of CSS color custom properties.
This is easier now! π Can we do it with CSS only? π€
:has() Rocks!:has() Contrast?You get limited pretty fast by avoiding JavaScript.
Using :has() is magical, but only per-page.
The preference doesn't get saved while browsing the site.
You can't simply use <buttons> if you prefer those!
So what JavaScript do we need?
"JavaScript should only do what only JavaScript can do."
OK great, so now we can use buttons, but this still only works per-page, and is lost when the page is reloaded β»οΈ
So we need to store the preference for the user.
For this we can use sessionStorage or localStorage.
sessionStorage lasts per visit to a domain. localStorage is kept between visits until the user clears their browsing data.
localStorageThe three buttons are neat, but what is the current mode? π€
It's not too difficult to set a class with JS on the 'active' button.
But instead let's use the existing aria-pressed attribute!
This is exactly what it's for, and has the benefit that a screen reader can also announce which option is currently active.
aria-pressed for ButtonsUse color-scheme: light dark; for a free dark mode
Experiment with color-mix() with Canvas & CanvasText
Use light-dark() to get specific within each mode
Honour your users' colour and contrast preferences
Experiment and play :)
Genuinely honoured to have been invited to speak at CSS Day!
π Γ Γ Γ π
π sarajoy.dev
𦣠@sarajw@front-end.social
π¦ @sjoy.lol
π slides.sarajoy.dev/CSS-Day-2026