Managing scalable, consistent colors in your css-in-js app with Reshader

Imagine the following scenario: you have an app; this app uses styled-components, glamorous, emotion, or any other css-in-js library.

My question is: how do you deal with colors in this context?

I bet the answer would be something like this:

I have some .js files storing the CSS' variables, and one of them is a colors.js that exports a JS Object with the colors my app needs to display.

In case I'm right—or almost,—that's awesome—though you can go even further, and I'm going to show you how.—Otherwise, in case I'm wrong, allow me to spark in you an idea.

Let's start bringing that scenario to the real world by imaging your app has the following colors:

Then, let's say we have a folder UI that contains all the shared CSS(-in-JS) variables, and we store them on separate files. One of these files is called colors.js and with our colors it would look like this:

This is good and simple. You simply access your colors by calling colors.brand.yellow etc. Unfortunately, that's not enough and the reason is slightly obvious. For you to see the obviousness, would be easier if we proceed with examples. I've built this landing page to help us:

Pro tip: don't forget to inspect the code behind this page by seeing the source on CodeSandbox. You must do the same process for further previews in order to understand what's going on.

Cool, right?

But suddenly, we had the brilliant idea to add some borders to make it look cooler. A lighter yellow would be nice.

But, what lighter yellow?

As you may guess, we have to find it first because it doesn't exist. The issue is: which tone should we use? Mhmm. In a very trivial context, we'd just open up Sketch or Photoshop, input our brand's yellow in the color picker and adjust it a little to get something lighter. Let's say we came up with #FEE579. Then let's adaptar our colors.js:

I've changed things a bit. yellow is no longer a string, but an array with two elements. The first one (index zero) is our new tone, the brand's yellow with a lighter variation, and the second one is the main color, the one we were using before.

Here, of course, I've implemented my own approach to deal with colors using an array where the lower the index, the lighter the tone of that color is, but you could design whatever you want. We'll get into this further.

Okay, now that we have our lighter yellow set, let's add some borders and apply the new tone to it:

Oh yeah, now our landing page looks more authentic. That's nice. But a landing is not a landing without a catchy phrase and a call-to-action button:

Almost ready-to-deploy, right? Wrong. This button doesn't fit in a mainstream-startup-landing page. We must customize it to be cool.

Note: the white color on the text within the button was set in colors.js.

Though it looks better, we're still not there because we don't have hover, focus and active states on our Button styles. The thing is: once again we have to think about these other colors. Just as before, open a design tool, go for the color picker, input our brand's red and adjust it to reach the tone we want. After that, with the color in hands, we have to paste it on our code and make some visual tests and iterations to check if it's in harmony with the rest of the landing page as well as if it fits on our button's states.

For this example, I'll pick a lighter and a darker tone. Our colors.js will look like this:

Again, the lighter tone is on index 0 and the darker one is on index 2 of the red property to follow the lighter-to-darker logic.

Now, let's get back to apply the remaining styles on the Button component:

Wow! Ain't our button becoming nicer? But if you click on it, you'll realize that the :active state I promised to implement is still missing. That's because I haven't picked an even darker tone to set to the background-color of it. To resolve our problem, I've chosen #E21020, and this is how the colors.js looks now:

And here you go our Button component:

Eureka! Now we have a deployable landing page ready to convert opportunities into actual customers, or maybe open it to a VC.

In a parallel world, 'say it got launched. We've add Google Analytics and are eagerly waiting for visitors. One day has passed, two days, three days… mhmm, zero conversions so far. Weird—what's wrong? Our landing is awesome!

While staring at the screen thinking of why we failed, the notification widget on the top-right side of the screen just popped up.

An honest feedback from a potential customer

The email reads:

I'd just like to inform that your landing page doesn't work. The red color on your call-to-action button is too negative. You have to add something happier; something that encourages the user to go further instead of making him stop right there.
My advice as an UX expert would be make it green or something less negative. Be creative here, but change this color please.
<Random Name>, UX Master Brain Engineer Visual at

Brilliant!—the "click moment" suddenly happens to us,—we must turn that red into green!

Let's dive into our code, starting by changing, again, our colors.js:

Introducing the green property at line 11

Now all that's left is to change red into green on the Button component… mhmmm… wait… there are the other tones missing—the lighter and darker ones. Gonna open Sketch again, invoke the color picker, and adjust the bits to get nice tones. Let's try these:

And the Button now looks like this:

Mhmmm… I don't know… it seems way too bright and isn't in harmony with the environment nonetheless we loved this green tone and want to stick with it. Let's just invert things a little:

Oh, better, though not there yet. Will look up for another yellow tone, an even lighter one, to use it as the background yet keeping the border exactly the same.

The #F7F4E5 seems perfect! Let's prepend it to our yellow property at colors.js:

Now the result will be:

Not perfect, but better, more authentic. But there's something I don't like: the color of the catchy phrase. It has the same weight as our great Hello World copy, and it shouldn't.

I will just lighten my brand's tuna color a little, then update my colors.js again alongside to the Button component. The results are:

Tuna is now an array with a lighter tone out of the main's one

Phew! Let's ship it!

And then our app lived happily after. The UX guru was right: changing the colors a little has improved our game. Lots of conversions from day one (!).

The problem here is that playing with the colors of our fantasy app was exhaustive. Though it's a very minimal detail, repeating this process from time to time is overwhelming. I faced it several times, and even with apps like, the process was still annoying. I found out that I needed an easy, automatic solution that would ease this process programatically.

Introducing Reshader

Sorry about the long, step-by-step tutorial above. I had to do it to demonstrate that this simple thing of dealing with colors can be exhaustive, specially when we don't have a designer-force to give the shades for us.

To get this topic started, from now on, we're going to call the "tones" as "shades", ok? Semantically speaking, that's what they are. Tones are forks of a color, not necessarily darker or lighter, while shades are supposed to be a progressive mutation of that color necessarily with lighter or heavier weights.

Almost every website, app or system implements shades of colors, even when developers are not aware of that. These apps can have a main gray, and for the titles a darker shade is used whilst the paragraphs go with a lighter one. Same thing for buttons, modals, heroes, and almost every component out there.

Thankfully, introducing Reshader is the easiest part of this article. Shortly, it is a very new library—at the moment I'm writing this, I've released it publicly just a few days ago—to get the shades of colors. Its API is minimal, intuitive and well documented.

Moving straight to the hands-on, I'm going to use the same app/landing page we've built earlier to demonstrate how Reshader can optimize the process we used before.

First things first, let's install Reshader:

yarn add reshader or npm install reshader is enough. The only dependency is the Qix-'s Color, which is bundled in the installation. If you want to implement it with RequireJS or in your window, I'll invite you to manually download the distribution file.

After that, let's jump again to the colors.js of our app. With those same main colors, all we have to do is import Reshader and get the shades using it:

Note that I implemented Reshader for all the colors but white (no need for that)

And by applying these new colors and palettes, this is our new look and feel:

You might have noticed that things messed up a little, and you're not wrong. Essentially, this is what happened:

  1. Reshader automatically generates shades based on the given color progressively and consistently. We are no longer dealing with tones—random forks of a color,—but real shades. Learn more.
  2. The button is barely visible, the border isn't bold anymore and the background doesn't look yellow because of Reshader's default options.

Let's fix this.

Zooming the API's documentation a little, we'll find out that it has an option called numberOfVariations that says:

[numberOfVariations is] the number of variations you want to extract out of the color you entered. This option will get both lighter and darker shades in the same amount, meaning that if you set it as 10, it will get you 10 lighter shades and 10 darker shades.

To prepare you with some understanding in advance, previously, the yellow property returned an array with three elements within. The first one was the lightest shade, the second one was the main color, and the third one was the darkest shade. As I said, we've aimed that shape to have a logical, cohesive palette of the yellow color.

Fundamentally Reshader was designed and outputs based on this same logic. As you saw in the gist above, we're picking the .palette property of the reshader's function and setting it to our colors. This .palette returns an array with the shades and the color you passed in the first argument of thereshader's function. And as you may guess, it's sorted from the lightest shade to the darkest, not skipping the given color. Meaning that yellow[0] is the lightest shade and yellow[yellow.length — 1] is the darkest.

Getting back to the numberOfVariations… by default, its value is 5. According to the documentation, this means that it’ll get 5 lighter shades and 5 darker shades of the given color. (By reading this out loud, I think there’s an opportunity to make this option name better.)

That being said, thanks to the numberOfVariations option, we no longer have 3 variations of the yellow color anymore. The amount of variations of the other colors changed as well, meaning that now they all are arrays of 11 elements: 5 lighter shades, the main color and 5 darker shades.

By changing the wires a bit on our Button.js file in accordance to the Reshader output (changing the indices), this is our new visual:

Pretty similar to the last results, right? To highlight better how simple managing colors with Reshader is, let's remove a few colors of our brand's palette. Now it will only consist of tuna and blue:

And after refashioning the landing page with these new colors and shades, this is how it looks:

Easy, ain't it?

The key differences of Reshader's approach to the manual one are:

  1. You don't have to worry about the shades anymore. They just work.
  2. It's easy to scale and maintain.
  3. Getting different results depends only on changing the options of Reshader's API.
  4. As I mentioned before, the shades will always be consistent. Meaning they'll always be darker or lighter versions of the given color. This sounds confusing, right? But what I meant is that when you pick a color by the eye, it's not a robot projected to lighten or darken a color. It is very arbitrary and oftentimes people that are more into design will spot these color inconsistencies with ease.
  5. Theming gets easy to achieve.
  6. Accessible colors are easy to achieve as well. There's an option called contrastRatio that you can play with to get accessible palettes.

But as usual, life is not a bed of roses. I have to be transparent and highlight the main downsides of using Reshader at the current state:

  1. If you're pairing with a designer, in order to use Reshader, you really must pair with him and I mean it. The process gets rougher to his side: basically, he has to pass you the main colors and you have to pass back the shades of them which were generated by Reshader. Unfortunately, at the moment this article was written, there was no out-of-the-box shortcuts.
  2. Although you can do some magic tricks to get it working with your CSS/SASS/LESS environment, Reshader was initially designed with css-in-js in mind. This means that you're gonna have some extra effort to translate its outputs into variables of different style systems.

For both cases, I promise I'm thinking in solutions. Feel free to submit your ideas to the Reshader's repo on GitHub.


Please, just don't run reshader several times within your render method and you'll be fine. Read more.

The future of Reshader

Reshader is very fresh. Though its API is already stable, there are several ideas on my mind of features that'll optimize the process of handling colors even more. As I said before, currently I'm focused on figuring solutions for the current most common problems the library has.

Apart of these solutions, I'm collecting feedback to pinpoint the features that are requested most by fellow developers. Again, feel free to submit your ideas.

To conclude

Ideally, Reshader will come in handy mostly for projects that cares with colors and operates with the css-in-js approach—specially those without a design crew. Also, if somehow you want to programatically generate shades of colors, it'd be a way-to-go.

If you have any questions, write a comment and I’ll respond as soon as I see it.

Want to give Reshader a star or read its full documentation? Make and the repository yours.

Topics of interest

More Related Stories