Investigating dark mode for SVG's in GitHub readme's

Published on June 13, 2021

This blog post is about a (failed) attempt at introducing dark mode for our logo SVG's in the readme files of our open source libraries at Laravel. I thought it would be interesting to share what I learned during the attempt and what's currently preventing us from introducing them.

Prelude

Almost all of the readme-files in our repositories have a header with the logo of the project and some badges like this:

These feature a transparent logo SVG with an icon and dark letters which is loaded through an img element:

<p align="center">
    <img src="/art/logo.svg" alt="Logo Laravel Cashier Stripe">
</p>

This has always worked well because GitHub always used to use a white background with dark text for its readme files. That is until they introduced dark mode. This meant that users could now toggle to a new dark-styled theme of GitHub. But because of this change our logos now look like below when dark mode is enabled:

I was wondering if we could fix that. In the end, SVG consists of tags and you can already use basic CSS and attributes like style in them (although not everything is supported). So I set out on an attempt to try to toggle the lettering color based on when dark mode was enabled or not. Except things didn't go exactly how I thought they'd go.

Attempt #1: Media Queries

The most basic way in CSS to detect dark mode is through a media query. The below is a simplified version of the source code of the SVG logos we use:

<svg xmlns="http://www.w3.org/2000/svg" width="360" height="64" viewBox="0 0 360 64">
    <!-- Icon Specific paths and definition tags. -->
	
    <path fill="#2E2E2E" d="..."/>
</svg>

It opens up with the svg tag which contains the xmlns definition and the width & height attributes as well as the viewBox. Inside the SVG icon, there are specific paths and definition tags that shape up the icon of the logo. And lastly, we have the path that shapes the letters. It has a fill attribute that indicates what solid color the letters should be filled with.

To even simplify the examples in this post more I'll omit some more things from the icon and continue with the following base example:

<svg>
    <path fill="#2E2E2E" d="..."/>
</svg>

Now, my first attempt was to introducing media queries. I figured that if the web page detected that dark mode was enabled then the GitHub page would be in dark mode as well. So I ended up writing the following:

<svg>
    <style>
        .letters { fill: #2E2E2E; }

        @media (prefers-color-scheme: dark) {
            .letters { fill: #ffffff; }
        }
    </style>
    
    <path class="letters" d="..."/>
</svg>

And this worked surprisingly well! When I rolled this out to the repo I could see that depending on the system's dark mode state the letters would turn white or black. Unfortunately, this only worked in Chrome and Firefox and I wasn't able to get it to work in Safari which I found out. I'm thankful to Amelia Bellamy-Royds for explaining to me that this could potentially be a browser bug). But that was a little beyond me to look into. In the worst case, people on Safari wouldn't be able to enjoy the new dark mode logos.

I started to roll this out to our repositories when community member Bruno Gaspar informed me of a use case I hadn't considered yet. It turns out that I had forgotten about the fact that if someone has explicitly set their GitHub theme to either dark or light, that the media query would still toggle based on the system's state. This caused the logo to look like below when a user's system was in dark mode but its GitHub theme was explicitly set to light:

Attempt #2: Data Attributes

It was an obvious use case but one I had indeed missed. But I thought to myself: "challenge accepted" and set out to figure this one out.

It turns out that GitHub defines what state it was in by setting data attributes on its html element. This was useful because with this I could both check the system's state and the web page's state to toggle the lettering color.

<!DOCTYPE html>
<html
    lang="en"
    data-color-mode="auto"
    data-light-theme="light"
    data-dark-theme="dark_dimmed"
>

So we see that GitHub has the following attributes defined: data-color-mode, data-light-theme, and data-dark-theme. The only useful one to us is data-color-mode because the other two just define which the specific color theme the user prefers for either light or dark mode. data-color-mode has the following three values: light, dark, and auto which is the preference that the user has set for toggling dark mode.

Okay, now that I had that information I modified the SVG logo as follows:

<svg>
    <style>
        .letters { fill: #2E2E2E; }

        @media (prefers-color-scheme: dark) {
            html[data-color-mode="auto"] .letters {
                fill: #ffffff;
            }
        }

        html[data-color-mode="dark"] .letters, {
            fill: #ffffff;
        }
    </style>
    
    <path class="letters" d="..."/>
</svg>

So let's explain the reasoning for the above-chosen styling. First of all, by default, the letters keep their original appearance for light mode which are dark letters. Then, we'll check if the system is in dark mode. If it is, and the user's color theme switches automatically we know that GitHub will be in dark mode as well. So we apply white lettering. Thirdly, should GitHub explicitly be in dark mode, we'll always apply white lettering. This should cover all of the use cases we need.

I was happy to find a solution and that GitHub made it easy to detect its active theme type so I eagerly committed a logo and refreshed my browser. Except... nothing happened. Even more so, the previous media query didn't seem to work anymore and the letters stayed dark. What was going on?

Eventually, after researching some, it turns out that SVG's referenced through img elements don't inherit parent DOM elements. So it would make sense that the previous media query was broken and that the attributes didn't work because the SVG simply couldn't read them.

This was quite the bummer. Even more so because GitHub doesn't render inline SVG's in its readme files.

Conclusion

The last thing I tried was trying to find a universal color for the lettering that'd look good in both dark and light mode. But it was always either too dark or too light. I tried adopting Laravel's color that's also used in the SVG logo on the laravel/laravel repository but it just didn't fit in with the icons of the other libraries.

So in the end I had to throw in the towel and revert the logos to their original state. I did learn a lot from this however and wanted to share this with you all. Who knows, maybe it could be useful to some.

If you found this article useful, or if you know some different ideas, definitely let me know on Twitter and follow me if you want to know what I'm up to next. Thanks for reading!

← Previous post

Releasing Blade Icons v1.0

About Me

I'm a software engineer from Antwerp, Belgium. I work as one of the core team members of Laravel, the popular PHP framework. I'm also the creator of Blade UI Kit, a set of renderless components to utilise in your Laravel Blade views.

My passions are open-source, building communities, managing software teams, and creating quality and maintainable products.

Together with my buddies Rias and Freek I organize events for Full Stack Belgium in the cities of Antwerp and Ghent. And we also organise Full Stack Europe, the conference for the whole team.

Follow me on Twitter: @driesvints.

© Dries Vints