Randoma11y - Accessible color combinations
Unusual colour combinations that are also accessible—keep smashing that “New colors” button.
Unusual colour combinations that are also accessible—keep smashing that “New colors” button.
This is a terrrific presentation by Chris, going through some practical implementations of modern CSS: logical properties, viewport units, grid, subgrid, container queries, cascade layers, new colour spaces, and view transitions.
While I’m talking about the SVGs on The Session, I thought I’d share something else related to the rendering of the sheet music.
Like I said, I use the brilliant abcjs JavaScript library. It converts ABC notation into sheet music on the fly, which still blows my mind.
If you view source on the rendered SVG, you’ll see that the path
and rect
elements have been hard-coded with a colour value of #000000
. That makes sense. You’d want to display sheet music on a light background, probably white. So it seems like a safe assumption.
Ah, but when it comes to front-end development, assumptions are like little hidden bombs just waiting to go off!
I got an email the other day:
Hi Jeremy,
I have vision problems, so I need to use high-contrast mode (using Windows 11). In high-contrast mode, the sheet-music view is just black!
Doh! All my CSS adapts just fine to high-contrast mode, but those hardcoded hex values in the SVG aren’t going to be affected by high-contrtast mode.
Stepping back, the underlying problem was that I didn’t have a full separation of concerns. Most of my styling information was in my CSS, but not all. Those hex values in the SVG should really be encoded in my style sheet.
I couldn’t remove the hardcoded hex values—not without messing around with JavaScript beyond my comprehension—so I made the fix in CSS:
[fill="#000000"] {
fill: currentColor;
}
[stroke="#000000"] {
stroke: currentColor;
}
That seemed to do the trick. I wrote back to the person who had emailed me, and they were pleased as punch:
Well done, Thanks! The staff, dots, etc. all appear as white on a black background. When I click “Print”, it looks like it still comes out black on a white background, as expected.
I’m very grateful that they brought the issue to my attention. If they hadn’t, that assumption would still be lying in wait, preparing to ambush someone else.
In design, both in the digital and physical worlds, color should never be the sole indicator of meaning. A simple test: if your work was converted to grayscale, would it still be usable?
Andy describes life online with deuteranopia and dispenses some practical advice for designers:
If there’s any uncertainty, adding labels, icons, or textures to each meaningful color of your design will make it accessible to many more people, regardless of their ability to perceive color.
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.)
A wonderful bit of spelunking into the annals of software interfaces by Elise Blanchard.
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.
Accidental colour palettes captured in the wild.
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.
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.
A handy tool for getting an overview of your site’s CSS:
CSS Stats provides analytics and visualizations for your stylesheets. This information can be used to improve consistency in your design, track performance of your app, and diagnose complex areas before it snowballs out of control.
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.
I’m not the only one swapping out Sass with CSS for colour functions:
Because of the declarative nature of CSS, you’re never going to get something as terse as what you could get in Sass. So sure, you’re typing more characters. But you know what you’re not doing? Wrangling build plugins and updating dependencies to get Sass to build. What you write gets shipped directly to the browser and works as-is, now and for eternity. It’s hard to say that about your Sass code.
I wrote recently about moving away from Sass to using native CSS features. I had this to say on the topic of mixins in Sass:
These can be very useful, but now there’s a lot that you can do just in CSS with
calc()
. The built-indarken()
andlighten()
mixins are handy though when it comes to colours.
I know we will be getting these in the future but we’re not there yet with CSS.
Anyway, I had all this in the back of my mind when I was reading Lea’s excellent feature in this month’s Increment: A user’s guide to CSS variables. She’s written about a really clever technique of combining custom properites with hsl()
colour values for creating colour palettes. (See also: Una’s post on dynamic colour theming with pure CSS.)
As so often happens when I’m reading something written by Lea—or seeing her give a talk—light bulbs started popping over my head (my usual response to Lea’s knowledge bombs is either “I didn’t know you could do that!” or “I never thought of doing that!”).
I immediately set about implementing this technique over on The Session. The trick here is to use separate custom properties for the hue, saturation, and lightness parts of hsl()
colour values. Then, when you want to lighten or darken the colour—say, on hover—you can update the lightness part.
I’ve made a Codepen to show what I’m doing.
Let’s say I’m styling a button
element. I make custom propertes for hsl()
values:
button {
--button-colour-hue: 19;
--button-colour-saturation: 82%;
--button-colour-lightness: 38%;
background-color: hsl(
var(--button-colour-hue),
var(--button-colour-saturation),
var(--button-colour-lightness)
);
}
For my buttons, I want the borders to be slightly darker than the background colour. When I was using Sass, I used the darken()
function to this. Now I use calc()
. Here’s how I make the borders 10% darker:
border-color: hsl(
var(--button-colour-hue),
var(--button-colour-saturation),
calc(var(--button-colour-lightness) - 10%)
);
That calc()
function is substracting a percentage from a percentage: 38% minus 10% in this case. The borders will have a lightness of 28%.
I make the bottom border even darker and the top border lighter to give a feeling of depth.
On The Session there’s a “cancel” button style that’s deep red.
Here’s how I set its colour:
.cancel {
--button-colour-hue: 0;
--button-colour-saturation: 100%;
--button-colour-lightness: 40%;
}
That’s it. The existing button declarations take care of assigning the right shades for the border colours.
Here’s another example. Site admins see buttons for some actions only available to them. I want those buttons to have their own colour:
.admin {
--button-colour-hue: 45;
--button-colour-saturation: 100%;
--button-colour-lightness: 40%;
}
You get the idea. It doesn’t matter how many differently-coloured buttons I create, the effect of darkening or lightening their borders is all taken care of.
So it turns out that the lighten()
and darken()
functions from Sass are available to us in CSS by using a combination of custom properties, hsl()
, and calc()
.
I’m also using this combination to lighten or darken background and border colours on :hover
. You can poke around the Codepen if you want to see that in action.
I love seeing the combinatorial power of these different bits of CSS coming together. It really is a remarkably powerful programming language.
A great little mini case-study from Eric—if you’re exporting transparent PNGs from a graphic design tool, double-check the colour-depth settings!
I’d been saving the PNGs with no bit depth restrictions, meaning the color table was holding space for 224 colors. That’s… a lot of colors, roughly 224 of which I wasn’t actually using.
I never thought of combining the datalist
element with input type="color"
—it’s pretty cool that it just works!
I had a very productive time at Indie Web Camp Amsterdam. The format really lends itself to getting the most of a weekend—one day of discussions followed by one day of hands-on making and doing. You should definitely come along to Indie Web Camp Brighton on October 19th and 20th to experience it for yourself.
By the end of the “doing” day, I had something fun to demo—a dark mode for my website.
Y’know, when I first heard about Apple adding dark mode to their OS—and also to CSS—I thought, “Oh, great, Apple are making shit up again!” But then I realised that, like user style sheets, this is one more reminder to designers and developers that they don’t get the last word—users do.
Applying the dark mode styles is pretty straightforward in theory. You put the styles inside this media query:
@media (prefers-color-scheme: dark) {
...
}
Rather than over-riding every instance of a colour in my style sheet, I decided I’d do a little bit of refactoring first and switch to using CSS custom properties (or variables, if you will).
:root {
--background-color: #fff;
--text-color: #333;
--link-color: #b52;
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
a {
color: var(--link-color);
}
Then I can over-ride the custom properties without having to touch the already-declared styles:
@media (prefers-color-scheme: dark) {
:root {
--background-color: #111416
--text-color: #ccc;
--link-color: #f96;
}
}
All in all, I have about a dozen custom properties for colours—variations for text, backgrounds, and interface elements like links and buttons.
By using custom properties and the prefers-color-scheme
media query, I was 90% of the way there. But the devil is in the details.
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);
}
The real challenge came with the images I use in the headers of my pages. They’re JPEGs with white corners on one side and white gradients on the other.
I could make them PNGs to get transparency, but the file size would shoot up—they’re photographic images (with a little bit of scan-line treatment) so JPEGs (or WEBPs) are the better format. Then I realised I could use CSS to recreate the two effects:
clip-path
.background-image: linear-gradient(
to right,
transparent 50%,
var(—background-color) 100%
);
Oh, and I noticed that when I applied the clip-path
for the corners, it had no effect in Safari. It turns out that after half a decade of support, it still only exists with -webkit
prefix. That’s just ridiculous. At this point we should be burning vendor prefixes with fire. I can’t believe that Apple still ships standardised CSS properties that only work with a prefix.
In order to apply the CSS clip-path
and gradient, I needed to save out the images again, this time without the effects baked in. I found the original Photoshop file I used to export the images. But I don’t have a copy of Photoshop any more. I haven’t had a copy of Photoshop since Adobe switched to their Mafia model of pricing. A quick bit of searching turned up Photopea, which is pretty much an entire recreation of Photoshop in the browser. I was able to open my old PSD file and re-export my images.
Let’s just take a moment here to pause and reflect on the fact that we can now use CSS to create all sorts of effects that previously required a graphic design tool like Photoshop. I could probably do those raster scan lines with CSS if I were smart enough.
This is what I demo’d at the end of Indie Web Camp Amsterdam, and I was pleased with the results. But fate had an extra bit of good timing in store for me.
The very next day at the View Source conference, Melanie Richards gave a fantastic talk called The Tailored Web: Effectively Honoring Visual Preferences (seriously, conference organisers, you want this talk on your line-up). It was packed with great insights and advice on impementing dark mode, like this little gem for adjusting images:
@media (prefers-color-scheme: dark) {
img {
filter: brightness(.8) contrast(1.2);
}
}
Melanie also pointed out that you can indicate the presence of dark mode styles to browsers, although the mechanism is yet to shake out. You can do it in CSS:
:root {
color-scheme: light dark;
}
But you can also do it in HTML:
That allows browsers to swap out replaced content; interface elements like form fields and dropdowns.
Oh, and one other addition I added after the fact was swapping out map imagery by using the picture
element to point to darker map tiles:
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.mapbox.com/styles/v1/mapbox/dark-v10/static...">
<img src="https://api.mapbox.com/styles/v1/mapbox/outdoors-v10/static..." alt="map">
</picture>
So now I’ve got a dark mode for my website. Admittedly, it’s for just one of the eight style sheets. I’ve decided that, while I’ll update my default styles at every opportunity, I’m going to preservethe other skins as they are, like the historical museum pieces they are.
If you’re on the latest version of iOS, go ahead and toggle the light and dark options in your system preferences to flip between this site’s colour schemes.
A deep, deep, deep dive into the JPEG format. Best of all, it’s got interactive explanations you can tinker with, a la Nicky Case or Bret Victor.
Wheeee! Another fun experiment from Cameron.