Dark Mode Toggles Should be a Browser Feature – Bram.us
This is a thoughtful proposal for a browser feature from Bram. Very convincing!
This is a thoughtful proposal for a browser feature from Bram. Very convincing!
A potted history of communication networks from the pony express and the telegraph to ethernet and wi-fi.
It’s said that the best way to learn about something is to teach it. I certainly found that to be true when I was writing the web.dev course on responsive design.
I felt fairly confident about some of the topics, but I felt somewhat out of my depth when it came to some of the newer modern additions to browsers. The last few modules in particular were unexplored areas for me, with topics like screen configurations and media features. I learned a lot about those topics by writing about them.
Best of all, I got to put my new-found knowledge to use! Here’s how…
The Session is a progressive web app. If you add it to the home screen of your mobile device, then when you launch the site by tapping on its icon, it behaves just like a native app.
In the web app manifest file for The Session, the display-mode
property is set to “standalone.” That means it will launch without any browser chrome: no address bar and no back button. It’s up to me to provide the functionality that the browser usually takes care of.
So I added a back button in the navigation interface. It only appears on small screens.
Do you see the assumption I made?
I figured that the back button was most necessary in the situation where the site had been added to the home screen. That only happens on mobile devices, right?
Nope. If you’re using Chrome or Edge on a desktop device, you will be actively encourged to “install” The Session. If you do that, then just as on mobile, the site will behave like a standalone native app and launch without any browser chrome.
So desktop users who install the progressive web app don’t get any back button (because in my CSS I declare that the back button in the interface should only appear on small screens).
I was alerted to this issue on The Session:
It downloaded for me but there’s a bug, Jeremy - there doesn’t seem to be a way to go back.
Luckily, this happened as I was writing the module on media features. I knew exactly how to solve this problem because now I knew about the existence of the display-mode
media feature. It allows you to write media queries that match the possible values of display-mode
in a web app manifest:
.goback {
display: none;
}
@media (display-mode: standalone) {
.goback {
display: inline;
}
}
Now the back button shows up if you “install” The Session, regardless of whether that’s on mobile or desktop.
Previously I made the mistake of inferring whether or not to show the back button based on screen size. But the display-mode
media feature allowed me to test the actual condition I cared about: is this user navigating in standalone mode?
If I hadn’t been writing about media features, I don’t think I would’ve been able to solve the problem. It’s a really good feeling when you’ve just learned something new, and then you immediately find exactly the right use case for it!
Last month I wrote about writing on web.dev. At that time, the first five parts of a fourteen-part course on responsive design had been published. I’m pleased to say that the next five parts are now available. They are:
It wasn’t planned, but these five modules feel like they belong together. The first five modules were concerned with layout tools—media queries, flexbox, grid, and even container queries. The latest five modules are about the individual elements of design—type, colour, and images. But those elements are examined through the lens of responsiveness; responsive typography with clamp
, responsive colour with prefers-color-scheme
, and responsive images with picture
and srcset
.
The final five modules should be available later this month. In the mean time, I hope you like the first ten modules.
I like this high-level view of the state of CSS today. There are two main takeaways:
This is exactly the direction we should be going in! More and more power from the native web technologies (while still remaining learnable), with less and less reliance on tooling. For CSS, the tools have been like polyfills that we can now start to remove.
Alas, while the same should be true of JavaScript (there’s so much you can do in native JavaScript now), people seem to have tied their entire identities to the tooling they use.
They could learn a thing or two from the trajectory of CSS: treat your frameworks as cattle, not pets.
At first glance, this looks like a terrible idea. But the key is in the implementation. In this case, the implementation is truly awful.
The section on detecting “auto dark theme” is, as far as I can tell, not intended as a joke.
Mind you, this could all be a galaxy-brain idea to encourage more developers to provide their own dark mode styles. (In much the same way that AMP was supposed to encourage better performance.)
This is such a handy tool for building forms! Choose different combinations of type
, inputmode
, and autocomplete
attributes on input
elements and see how that will be conveyed to users on iOS and Android devices.
There’s a good discussion here (kicked off by Jen) about providing different theme-color
values in a web app manifest to match prefers-color-scheme
in media queries.
The more I consume content in reading apps, the more I am reminded of the importance and the power of progressive enhancement as a strategy to create resilient and malleable experiences that work for everyone, regardless of how they choose to consume our content.
Top stuff from Sara here!
We have a tendency to always make an assumption about how our readers are reading our content—probably in the browser, with our fancy styles applied to it. But if we make a habit out of thinking about the Web in layers and CSS as an enhancement on top of the content layer, then we can start optimizing and enhancing our users’ reading experiences regardless of their context.
Thinking about the different ways in which users access the Web only shines light on the importance of a progressively enhanced approach to building for the Web. The more we think about the Web in layers and try to improve the experience of one layer before moving to the next, the more resilient experiences we can create. That’s what the essence of progressive enhancement is about.
Robin makes a good point here about using dark mode thinking as a way to uncover any assumptions you might have unwittingly baked into your design:
Given its recent popularity, you might believe dark mode is a fad. But from a design perspective, dark mode is exceptionally useful. That’s because a big part of design is about building relationships between colors. And so implementing dark mode essentially forced everyone on the team to think long, hard, and consistently about our front-end design components. In short, dark mode helped our design system not only look good, but make sense.
So even if you don’t actually implement dark mode, acting as though it’s there will give you a solid base to build in.
I did something similar with the back end of Huffduffer and The Session—from day one, I built them as though the interface would be available in multiple languages. I never implemented multi-language support, but just the awareness of it saved me from baking in any shortcuts or assumptions, and enforced a good model/view/controller separation.
For most front-end codebases, the design of your color system shows you where your radioactive styles are. It shows you how things are tied together, and what depends on what.
It’s official. The extremely niche browser behaviour I documented is a bug.
Oh boy, do I have some obscure browser behaviour for you!
To set the scene…
I’ve been writing here in my online journal for almost twenty years. The official anniversary will be on September 30th. But this website has been even online longer than that, just in a very different form.
Here’s the first version of adactio.com.
Like a tour guide taking you around the ruins of some lost ancient civilisation, let me point out some interesting features:
.shtml
file extension. That means it was once using Apache’s server-side includes, a simple way of repeating chunks of markup across pages. Scientists have been trying to reproduce the wisdom of the ancients using modern technology ever since.100vw
and 100vh
? Well, this was long before viewport units existed. In fact there is no CSS at all on that page. It’s one big table
element with 100% width and 100% height.border-radius
coming from? Let me introduce you to an old friend—the non-animated GIF. It’s got just enough transparency (though not proper alpha transparency) to fake rounded corners between two solid colours.if (navigator.appName == "Netscape")
Now if your constitution was able to withstand that, brace yourself for what happens when you click on either of the two links, deutsch or english.
You find yourself inside a frameset. You may also experience some disorienting “DHTML”—the marketing term given to any combination of JavaScript and positioning in the late ’90s.
Note that these are not iframes, they are frames. Different thing. You could create single page apps long before Ajax was a twinkle in Jesse James Garrett’s eye.
If you view source, you’ll see a React-like component system. Each frameset
component contains frame
components that are isolated from one another. They’re like web components. Each frame has its own (non-shadow) DOM. That’s because each frame is actually a separate web page. If you right-click on any of the frames, your browser should give the option to view the framed document in its own tab or window.
Now for the part where modern and ancient technologies collide…
If you’re looking at the frameset URL in Firefox or Safari, everything displays as it should in all its ancient glory. But if you’re looking in Google Chrome and you’ve visited adactio.com before, something very odd happens.
Each frame of the frameset displays my custom offline page. The only way that could be served up is through my service worker script. You can verify this by opening the framest URL in an incognito window—everything works fine when no service worker has been registered.
I have no idea why this is happening. My service worker logic is saying “if there’s a request for a web page, try fetching it from the network, otherwise look in the cache, otherwise show an offline page.” But if those page requests are initiated by a frame
element, it goes straight to showing the offline page.
Is this a bug? Or perhaps this is the correct behaviour for some security reason? I have no idea.
I wonder if anyone has ever come across this before. It’s a very strange combination of factors:
I could submit a bug report about this but I fear I would be laughed out of the bug tracker.
Still …the World Wide Web is remarkable for its backward compatibility. This behaviour is unusual because browser makers are at pains to support existing content and never break the web.
Technically a modern website (one that registers a service worker) shouldn’t be using deprecated technology like frames. But browsers still need to be able support those old technologies in order to render old websites.
This situation has only arisen because the same domain—adactio.com—is host to a modern website and a really old one.
Maybe Chrome is behaving strangely because I’ve built my online home on ancient burial ground.
Update: Both Remy and Jake did some debugging and found the issue…
It’s all to do with navigation preloads and the value of event.preloadResponse
, which I believe is only supported in Chrome which would explain the differences between browsers.
According to this post by Jake:
event.preloadResponse is a promise that resolves with a response, if:
- Navigation preload is enabled.
- The request is a GET request.
- The request is a navigation request (which browsers generate when they’re loading pages, including iframes).
Otherwise
event.preloadResponse
is still there, but it resolves withundefined
.
Notice that iframes are mentioned, but not frames.
My code was assuming that if event.preloadRepsonse
exists in my block of code for responding to page requests, then there’d be a response. But if the request was initiated from a frameset, it is a request for a page and event.preloadRepsonse
does exist …but it’s undefined.
I’ve updated my code now to check this assumption (and fall back to fetch
).
This may technically still be a bug though. Shouldn’t a page loaded from a frameset count as a navigation request?
I added a dark mode to my site last year. Since then I’ve been idly toying with the addition of a dark mode to The Session too.
As with this site, the key to adding a dark mode was switching to custom properties for color
and background-color
declarations. But my plans kept getting derailed by the sheet music on the site. The sheet music is delivered as SVG generated by ABCJS which hard-codes the colour in stroke
and fill
attributes:
fill="#000000" stroke="#000000"
When I was describing CSS recently I mentioned the high specifity of inline styles:
Whereas external CSS and embedded CSS don’t have any effect on specificity, inline styles are positively radioactive with specificity.
Given that harsh fact of life, I figured it would be nigh-on impossible to over-ride the colour of the sheetmusic. But then I realised I was an idiot.
The stroke
and fill
attributes in SVG are presentational but they aren’t inline styles. They’re attributes. They have no affect on specifity. I can easily over-ride them in an external style sheet.
In fact, if I had actually remembered what I wrote when I was adding a dark mode to adactio.com, I could’ve saved myself some time:
I have SVGs of sparklines on my homepage. The SVG has a hard-coded colour value in the stroke attribute of the path element that draws the sparkline. Fortunately, this can be over-ridden in the style sheet:
svg.activity-sparkline path {
stroke: var(--text-color);
}
I was able to do something similar on The Session. I used the handy currentColor
keyword in CSS so that the sheet music matched the colour of the text:
svg path {
fill: currentColor;
}
svg path:not(stroke="none") {
stroke: currentColor;
}
Et voila! I now had light-on-dark sheet music for The Session’s dark mode all wrapped up in a prefers-color-scheme: dark
media query.
I pushed out out the new feature and started getting feedback. It could be best summarised as “Thanks. I hate it.”
It turns out that while people were perfectly fine with a dark mode that inverts the colours of text, it felt really weird and icky to do the same with sheet music.
On the one hand, this seems odd. After all, sheet music is a writing system like any other. If you’re fine with light text on a dark background, why doesn’t that hold for light sheet music on a dark background?
But on the other hand, sheet music is also like an image. And we don’t invert the colours of our images when we add a dark mode to our CSS.
With that in mind, I went back to the drawing board and this time treated the sheet music SVGs as being intrinsicly dark-on-light, rather than a stylistic choice. It meant a few more CSS rules, but I’m happy with the final result. You can see it in action by visiting a tune page and toggling your device’s “appearance” settings between light and dark.
If you’re a member of The Session, I also added a toggle switch to your member profile so you can choose dark or light mode regardless of your device settings.
I’ll be moderating this online panel next week with Emma Boulton, Holly Habstritt Gaal, Jean Laleuf, and Lola Oyelayo-Pearson.
There are still some spots available—it’s free to register. The discussion won’t be made public; the Chatham House Rule applies.
I’m looking forward to it! Come along if you’re interested in the future of design teams.
What will the near-future look like for design teams? Join us as we explore how processes, team structures and culture might change as our industry matures and grows.
I added a dark mode to my website a while back. It was a fun thing to do during Indie Web Camp Amsterdam last year.
I tied the colour scheme to the operating system level. If you choose a dark mode in your OS, my website will adjust automatically thanks to the prefers-color-scheme: dark
media query.
But I’ve seen notes from a few friends, not about my site specifically, but about how they like having an explicit toggle for dark mode (as well as the media query). Whenever I read those remarks, I’d think “I’m really not sure I’ve got time to deal with adding that kind of toggle to my site.”
But then I realised, “Jeremy, you absolute muffin! You’ve had a theme switcher on your website for almost two decades now!”
Doh! I had forgotten about that theme switcher. It dates back to the early days of CSS. I wanted my site to be a demonstration of how you could apply different styles to the same underlying markup (this was before the CSS Zen Garden came along). Those themes are very dated now, but if you like you can view my site with a Zeldman theme or a sci-fi theme.
To offer a dark-mode theme for my site, all I had to do was take the default stylesheet, pull out the custom properties from the prefers-color-scheme: dark
media query, and done. It took less than five minutes.
So if you want to view my site in dark mode, it’s one of the options in the “Customise” dropdown on every page of the website.
Tiny ruins.
This is such a clever use of variable fonts!
We can use a lighter font weight to make the text easier to read whenever dark mode is active.
Here’s one simple, practical way to make apps perform better on mobile devices: always configure HTML input fields with the correct
type
,inputmode
, andautocomplete
attributes. While these three attributes are often discussed in isolation, they make the most sense in the context of mobile user experience when you think of them as a team.
This is an excellent deep dive with great advice:
You may think that you are familiar with the basic
autocomplete
options, such as those that help the user fill in credit card numbers or address form fields, but I’d urge you to review them to make sure that you are aware of all of the options. The spec lists over 50 values!
It was a few years before I realized that worry stones had a name, that they were borrowed from cultures other and older than mine. Heck, it’s been more than a few years since I’ve even held one. But in the last few weeks, before and after launching the redesign, I’ve kept working away at this website, much as I’d distractedly run my fingers over a smooth, flat stone.