Hacker News new | past | comments | ask | show | jobs | submit login
Add a Dark Theme to a Ruby on Rails App (yann-defretin.medium.com)
73 points by hivacruz on March 16, 2021 | hide | past | favorite | 22 comments



At Brex, we released an experimental dark mode for our dashboard a while ago using these 4 lines of global CSS:

  body {
    background-color: black;
    filter: hue-rotate(180deg) invert(90%);
  }
We tweeted a video showing what it looks like here: https://twitter.com/derekstavis/status/1306365758411161605

A lot of customers were asking for it, but we weren't going to be able to implement it properly anytime soon, so this was the hacky compromise we came up with to deliver value as quickly as possible.

This has a bunch of edge cases you'll likely have to deal with case by case by applying the inverse filter `filter: invert(111%) hue-rotate(180deg);` selectively, but for us it actually worked shockingly well for the most part.

I suspect it will work best on the typical app design where most things are different shades of gray, and probably not as well on sites with backgrounds composed of non-gray hues (like right here on hacker news: https://imgur.com/a/jITAn6x) or apps with tons of user-generated videos/images.

If that fits the description of your app, I'd encourage you to give it a try, and maybe even ship it to customers (like we did) if it looks good enough! (I'd personally love to see some screenshots if you decide to do this!)

And of course, we're hiring: https://www.brex.com/careers/ ;)


An invert-rotate approach to a dark mode works great until you have any sort of layering (i.e. modals). This is where it really falls apart. Shadows become glows, and make it much harder to differentiate between layers.

If you're going for a quick hack like the above code, I'd recommend at least separating out your shadow styles into a css variable. I.e.

    :root {
      --shadow-color: #000;
    }

    .modal {
      box-shadow: 0 0 8px var(--shadow-color);
    }

    @media (prefers-color-scheme: dark) {
      :root {
        --shadow-color: #fff;
      }
      html {
        filter: hue-rotate(180deg) invert(1);
      }
    }


Thanks for the call out. I took a look at the modals in our dashboard and by default they have a super-subtle, barely visible shadow on top of an overlay, which looked fine.

I exaggerated the shadow a lot and this is what it ended up looking like: https://imgur.com/a/ustLofp

Definitely not pretty, but also probably not the end of the world. I can see this becoming problematic if we have more than a single additional layer, but we generally try to keep layering to a minimum so it hasn't been a problem for us yet.

Definitely something for folks looking to try this to keep in mind though.


This is a good corner cut, but in addition to other comments, there's two huge accessibility concerns, and one usability/design principle problem:

1. The color spaces available in CSS are not perceptually uniform. You may achieve some a11y checkbox with the inversion of luminosity/hue, but that doesn't mean it's actually readable.

2. Not all, but many, dark mode users prefer lower contrast in some areas to reduce brightness over all. This approach can tailor in the opposite direction.

3. In general with dark themes, it's still generally preferable for darker-in-light-mode things to be darker in dark mode. This does the opposite and can lead to confusing design structure.


These are all great points for anyone looking to try this to keep in mind.

For us, on points 1 and 2, we have done some due diligence on going through all the different pages/flows on our dashboard to make sure everything still looks good and text remains readable. A lot of folks internally prefer dark mode and dogfood it day to day which also helps a lot. So far we also haven't heard any major complaints re: readability/contrast from our customers, thankfully, but of course as soon as we do, we'll adjust and re-evaluate.

On point 3, this is where the inverse filter trick comes in handy, we've had to apply this in a few places where the inversion made things look awkward and/or unusable, most notably external partner logos and user-uploaded receipt pictures.

All of this is part of why it's still labeled as an experimental feature even months after release.


I’m super curious how much effort has gone into evaluating what works and making adjustments. I’m not trying to poo poo the idea if it’s working and doesn’t have a noticeable performance impact.

I know when I came back to web dev a year ago after five years fully backend this is exactly the idea I wanted to try. I asked the advice of a friend who’d stayed FE as dark mode rolled out and he said, basically it’s more trouble than it’s worth. I took that advice and built a dedicated dark mode for the project at that time. And I did so for my personal site which is a fairly intricate labor of design love (link in profile).

Honestly it was hardly any additional effort, and it wasn’t an afterthought. I prefer light mode generally but I like my own site’s dark mode better almost across the board. But there were so many details that I think would have been more work, and probably more difficult work, if I tried to start with filters and adjust.

FWIW, I don’t use it throughout but I do use it where I wanted programmatic control of color: I highly encourage looking at HSLuv or another perceptual color space to handle some of this.


The whole thing has been fairly hands-off for us, surprisingly enough, as I fully expected this to be a maintenance nightmare that we might have to pull back, hence the experimental label. I could count on a single hand the number of times we had to make adjustments since we shipped it.

I'd honestly love to be able to implement dark mode the proper way with semantic color tokens and runtime theme swapping someday. But since our codebase didn't start with hot-swappable theming in mind, retrofitting a proper theme-based dark mode into it would be a gigantic undertaking that we'd have trouble justifying against all of our other priorities.

For anyone building a brand new app, definitely start by implementing themes the right way instead of using this hack. You'll definitely save yourself a lot of headache down the line.


> But since our codebase didn't start with hot-swappable theming in mind

But it doesn’t have to be that thought out. It just needs the same kind of symmetry you’ve applied, wherever you’ve applied colors (and maybe lighten font weight a bit). You can just use a media query and target the same selectors.


There's probably this too: https://darkmodejs.learn.uno/


This certainly looks better than the one in the article!


I've been working on the dark mode implementation for Atlassian for the last year and a half, so this is right in my wheelhouse. This post is excellent advice for a small app, but if you have any production sized app or a larger team, it's not a great approach. Using colors directly in a theme override mean you have to then design and write the css for every page twice.

A better approach for doing dark mode is to use semantic tokens as a middleman. This works great not just for dark mode, but theming in general - allowing for high contrast mode, color blind modes, and others, while reducing the amount of effort required to style a new page (rather than growing multiplicatively with direct overrides).

If you're curious about this, I have a very basic schema I made for my app that you're welcome to steal here: https://github.com/jjcm/soci-frontend/blob/master/docs/token...

Here's an example implementation of the above schema: https://github.com/jjcm/soci-frontend/blob/master/soci-token...

The great thing is once it's implemented, it becomes VERY easy to know what token to use where. Got a button that's a primary action in a hover state? You probably want to use --brand-background-hover for the bg. Means you can do a lot of the design directly in code I find.


Hi, author here, thanks for your comments! I definitely understand that the alternatives seem to be better but in my case, as stated in the post, we are dealing with a very old design (that I didn't make by the way) where the two main solutions discussed here (invert, darkmode.js) don't work at all.

I'm not going to post screenshots but after a few tests, it just changes some elements but the overall design still needs to be redone, as dark, because many elements are "ignored" and not treated.

The only solution I see, as some of you wrote, would be to redone the design and make use of CSS variables. That will drastically reduce the amount of work to do to achieve a dark theme and the number of generated lines will be very small. I need to dig into this direction!


why not just use `:root`, define some variables for each color and media queries for prefer theme? I don't understand react theming or this approach, you can do it with 1 line of css.


Do you have an article that explains this approach in just a little more detail?



See https://developer.mozilla.org/en-US/docs/Web/CSS/:root#decla....

With :root, you can write css variables that can be accessed by any other element on the page (?).

So to implement a light and dark theme, you could have something like `@media (prefers-color-scheme: light){ :root { --bg-color: #FFF; }`, and then have the body element use that via `body { background: var(--bg-color); }`.


A quick implementation might be as simple as this: https://darkmodejs.learn.uno/


The thought of a backend technology knowing about my site's CSS theme makes me shudder.


well, it's a preference defined by your users, it makes sense to have a register about this on your backend.


Not the point of the article at all, but the annoying part of me feels compelled to point out a factual error:

Windows had dark mode long before copycats Android and iOS came on the scene with their implementations.

Windows Phone 7 had it back in 2010; dark was the default in part because it worked better for the displays in use at the time, and made the large bezels less noticeable.

Windows 10 launched with dark mode support in 2015. It was supported in the system shell, natively in UWP apps, and the setting was available to other apps through the Registry.

Dark mode support was even included on the Xbox One all the way back in 2013, although that is sort of cheating since they didn’t introduce light mode there until 2017.


Windows has had theming for the system UI (which could effectively be treated as a "dark mode") for ages - but the innovation of allowing apps (such as your web browser) to ask the OS for the user preference comes from (or at least was popularized by) mobile OSes.


That’s also not accurate. Browsers and apps have had the ability to ask for the system-defined light or dark mode since 2015. They simply chose not to until iOS could do it, so you’re right that the popularization came from mobile. Windows 10 was trying to be a mobile OS in 2015, so I suppose you might count dark/light theme support as a mobile innovation regardless.

Edit: the reason I mentioned it in the first place is that the author chose to lead the second paragraph of his article with a major factual error, and it’s worthwhile to have a clear understanding of the history. It’s like if I wrote an article about Jetpack Compose, and the second paragraph started with “Apple chose to follow the wildly successful trend of Android by coming out with the iPhone, and the success of Jetpack Compose by coming out with SwiftUI”. I’m disappointed that the only reaction to seeing that pointed out is for others to double down on the false assertions.

Edit 2: receipts, since I don’t feel like having to correct the record a third time: https://m.youtube.com/watch?v=EtXQVZ3iZWk




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: