mainLogo
Join Us On The Dark Side
Experimenting with CSS variables on April Fools Day
Engineering
Article

By Hizal Celik, Front End Engineer

For April Fools Day this year, the engineering team at Grata decided to surprise users with a dark theme of our app, welcoming them with a message from well known dark theme enthusiast:

"Join the Dark Side" by Darth Vader

To convert the entire application to a dark theme as quickly as possible, while still allowing users the ability to switch back to a light theme, we took advantage of a new technique for our codebase: CSS Variables.

To avoid having to individually add a dark version for every element and style in our platform, we decided to convert all existing color-related styling to reference variables instead. Doing so allows us to edit a color once and all areas in the platform where that is used would change together.

Given the inheritance rules for CSS variables, we were able to add a single class to <body> and override all existing colors with dark versions for each. And because our platform mainly uses different shades of grays, we can simply inverse the RGB values to achieve similar effects coming from the dark direction. So if we start with the value rgb(245,245,255) in the existing theme, we can calculate the distance from white for each color (so 255 - 245 = 10) and map that as a starting point from 0. Our example becomes rgb(10,10,20).

You may have noticed we didn't do that for the blue value; that is because we tend to add a bit of blue to all of our grays in the platform, and if we were to use the same logic as we do for red and green, it would change the tone of the platform to be more purple.

Comparison of dark and light themes

There were some cases where the grays had to be changed a bit to create a more natural look, and we ended up adding an offset value of 10 to prevent the platform from becoming too black. This allows for a more pleasant, dark-blue theme rather than a high-contrast, black theme, and also maintains the ability to add black shadows behind popups and tiles (a valuable UI aspect that is necessary to have elements stand out from each other). We also had to manually touch up a few elements that didn't stand out enough or looked odd with this mathematical inversion approach, stepping in for individually selected dark theme colors. So now, our platform looks a bit like this. We first define the raw RGB values upfront, and reference them in our color variables.

  /* brand colors */
  --grata-green: #26d9ca; /* darkened version of logo color */
  --grata-blue: #00a3d0; /* middle color in logo */
  --grata-dark-blue: #1072bd; /* dark blue in logo */
  --logo-text: #0b568f; /* text in logo */

  /* platform colors */
  --rgb-black:      0, 0, 0;
  --rgb-white:      255, 255, 255;
  --rgb-gray:       115, 115, 130;
  --rgb-gray-50:    50, 50, 60;
  ...
  --rgb-gray-253:   253, 253, 255;

  --black:          rgb(var(--rgb-black));
  --white:          rgb(var(--rgb-white));
  --gray:           rgb(var(--rgb-gray));
  --gray-50:        rgb(var(--rgb-gray-50));
  ...
  --gray-253:       rgb(var(--rgb-gray-253));
  --green:          #00C086;
  --limegreen:      #67CE1E;
  --seagreen:       #0D8BA5;
  --blue:           #08a1dd;
  --darkblue:       #3284E3;
  --orange:         #FF7F1D;
  --purple:         #574FB9;
  --magenta:        #BB62D3;
  --lilac:          #818AFE;
  --yellow:         #FFA400;
  --teal:           #13C2C2;
  --pink:           #E1528D;
  --red:            #F4342A;

Then for the dark theme, we override the raw RGB values, which are still referenced above by the color variables. By keeping the black-point offset a variable, we're able to dynamically shift the brightness of the dark theme without having to individually calculate or update each gray value.

html.dark {
  color: white;
  --logo-text:              var(--grata-dark-blue);
  --grata-dark-blue:        var(--grata-blue);
  --c-offset:       10;

  /* platform colors */
  --rgb-white:      20, 20, 30;
  --rgb-black:      calc(var(--c-offset) + 255), calc(var(--c-offset) + 255), calc(var(--c-offset) + 255);
  --rgb-gray:       calc(var(--c-offset) + 140), calc(var(--c-offset) + 140), calc(var(--c-offset) + 150);
  --rgb-gray-50:    calc(var(--c-offset) + 205), calc(var(--c-offset) + 205), calc(var(--c-offset) + 215);
  ...
  --rgb-gray-253:   calc(var(--c-offset) + 2), calc(var(--c-offset) + 2), calc(var(--c-offset) + 4);

  --box-shadow:     0 1px 6px 0 rgba(0, 0, 0, 0.56), 0 2px 32px 0 rgba(0, 0, 0, 0.66), inset 0 0 0 3px var(--gray-240);
  --tile-shadow:    0 -1px 3px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.62);
  ...
}

We'll be highlighting more of our engineering process, explorations through new technologies, challenges and more soon!

Want to learn more?

© 2021 Grata Inc.  All Rights Reserved. Privacy policy.