Skip to content

LESS Color Schemes using Grunt

Posted on:January 26, 2016

The Problem

This week I was searching for a way to modify my project to support color schemes. I had used LESS as my preprocessor, but after adding hundreds of color declarations, realized that color schemes would be a great feature. Looking around the internet, I found a bunch of references to methods that didn’t really fit what I was looking for. Namely, a lot of them recommended placing a class on the body tag, and doing color definitions as so:

body {
    .navbar(basketball, #ff9900);
    .navbar(football,   #99743d);
}

.navbar(@name, @color) {
    &.@{name} .navbar-inner {
        background-color: @color;
    }
}

This is great, but I wanted a way that required minimal modification of my LESS files (as there are many and the project is quite large. A simple find and replace would be of no use here.

So I set out to find a better way!

Enter Grunt-Contrib-Less

Grunt is awesome. I’ve been using it for a while in this project to run my watch, less, and uglify tasks, as well as tying it in to the Django runserver command so that they would run on the same thread (no more opening two windows to run the server and the build), but I digress.

Grunt’s LESS support comes from grunt-contrib-less. It supports most of the features of LESS, and luckily it happened to support the one command line flag I needed, modifyVars.

My project structure works like this:

.
├── color-schemes  <- color schemes!
│   ├── autumn.less
│   ├── endor.less
│   ├── florida.less
│   ├── gungan.less
│   └── pandora.less
├── components
│   ├── ... all my components
├── global
│   ├── animations.less
│   ├── colors.less
│   ├── fonts.less
│   ├── icons.less
│   ├── init.less
│   ├── mixins.less
│   └── shadows.less
├── main.less
├── pages
│   ├── ... all my pages
└── vendor
    └── custom.bootstrap.less

All the colors in my LESS project come from colors.less. It’s a large file with a few definitions at the top for five main colors:

@color-primary
@color-secondary
@color-success
@color-info
@color-warning
@color-error

Each color scheme defines five colors and I assign them to these variables. These variables are then used to indicate every other color or modification of a color in the project. That means that every color comes (at some point) from these definitions. It’s important to note that there are some modifications made by LESS’s fade, saturate, etc, so we can’t just swap out the colors on the front end.

modifyVars

Here’s where modifyVars comes in. I decided a better approach was to build multiple .css files. The goal was to build one file for each color scheme, so instead of app.css, I would have app.florida.css, app.pandora.css, and so on for each of my color schemes. Doing this was quite simple. First, I modified my colors.less to import a different color profile before my definitions. The color scheme would define the scheme colors and assign them to the main color variables. For example:

/* Gungan color set */

@set-color-1: #99B898;
@set-color-2: #FECEA8;
@set-color-3: #FF847C;
@set-color-4: #E84A5F;
@set-color-5: #2A363B;

/* Custom color set 
 * Use only these colors as a
 * basis for other colors. */

@color-primary:    @set-color-5;
@color-secondary:  @set-color-3;
@color-success:    @set-color-1;
@color-info:       @set-color-5;
@color-warning:    @set-color-2;
@color-error:      @set-color-4;

And colors.less:

@import 'color-schemes/@{color-scheme}.less';

/* Bootstrap Brand Colors */
@color-brand-primary: @color-primary;
@color-brand-success: @color-success;
@color-brand-info:    @color-info;
@color-brand-warning: @color-warning;
@color-brand-danger:  @color-danger;

/* Custom variables */
@sidebar-color:            @color-primary;
@input-border-color:       @color-secondary;
@input-border-focus-color: @color-success;
@error-color:              @color-error;

//... etc

Now I just modified my Gruntfile to build several versions of app.css by passing in a different color-scheme parameter, which matches the filenames in color-schemes/.

// grunt-contrib-less task
less: {
  florida: {
    options: {
        modifyVars: { // the magic
        'color-scheme': 'florida'
      },
    },
    files: {
      'build/css/app.florida.css': 'assets/less/main.less'
    }
  },
  gungan: {
    options: {
        modifyVars: {
        'color-scheme': 'gungan'
      },
    },
    files: {
      'build/css/app.gungan.css': 'assets/less/main.less'
    }
  },
// ... more color schemes

Now when I run my Grunt less command, I get a different CSS file for each color scheme.

Using The New Files

Now I needed a way to load these files on the front end. This will depend on your front end framework. There are a few options.

If you’re using Angular (like me), I would recommend using something like angular-css-injector. The preference for color scheme was part of my user object, which came down with some other site metadata (which included a default color scheme if the user’s preference was not set). That means all I had to do was update my MetaData Service to use angular-css-injector:

MetaDataService.prototype.updateColorScheme = function() {
	this.css.removeAll();
	if (this.user['color_scheme']) {
		this.css.add(this.user.color_scheme);
	} else if (this.metaData['color_scheme']) {
		this.css.add(this.metaData.color_scheme);
	}
}