Chris EK, on life as a continually learning software engineer.

A Single Set of Color Vars With PostCSS

Jun 30th, 2016

In this post, I describe how to create a single list of color variables (in JavaScript) so that those colors can be shared across JavaScript files and CSS stylesheets. Using PostCSS within a Webpack app, I outline the problem of sharing styles between CSS and JS and how it can be solved. For step-by-step code examples, skip ahead to “Enter PostCSS (Problem Solved)”.

The Problem We’re Solving

At my day job, we’d had a colors.css file for a while, where we define all the hex codes for our color scheme, as defined by our designers. It looked something like this:

For a long time, while our CSS colors were nicely organized, our JS colors weren’t. We have colors in our D3 visualizations and our inline styles on React components. As a simple improvement, I decided to pull all our colors into a single map that could be read by both our CSS and JS files. NB: this post is about CSS colors, but can apply to any CSS variables you’d like shared to JS.

Since I had previously been using cssnext, I followed [these migration steps](http://cssnext.io/postcss/#postcss-loader) to upgrade to postcss-cssnext. This meant swapping the `cssnext-loader` for `postcss-loader` in my Webpack loaders, removing cssnext from my webpack config, and adding the postcss options to webpack config:
(webpack.config.js)download

12345678910111213141516171819202122232425

module.exports={module:{loaders:[{test:/\.css$/,loader:"style-loader!css-loader!postcss-loader"}]},postcss:function(webpack){return[require("postcss-import")({addDependencyTo:webpack}),require("postcss-url")(),require("postcss-cssnext")({features:{customProperties:{variables:colorVars// `colorVars` will be defined above, see step 4.}}}),require("postcss-browser-reporter")(),require("postcss-reporter")(),]},}

Add a colors.js. I chose to CONSTANT_CASE the variables (for idiomatic JS).
(colors.js)download

// to be added aboce `module.exports` of webpack.config.jsvarcolorVars=require("./app/constants/colors")

(OPTIONAL) Because I wanted CSS variables to be lowercase and hyphen-separated so I could maintain our old `color: var(–light-grey);` syntax, I added a transformation from the constant case (using Ramda’s `curry` and `reduce`):
(case-transform.js)download

12345678910

// to be added above `module.exports` of webpack.config.js// color vars in JS are CONST_CASE, but need to be converted to hyphen-case for CSSconstrenameKeys=R.curry((renameFn,obj)=>{returnR.reduce((acc,key)=>{acc[renameFn(key)]=obj[key]returnacc},{},R.keys(obj))})constconstCaseToHyphenCase=(str)=>{returnstr.replace(/_/g,"-").toLowerCase()}varcolorVars=renameKeys(constCaseToHyphenCase,require("./app/constants/colors"))

/* and this still works, without even needing to `@import "../theme/colors";` */.my-special-component{background-color:var(--white);color:var(--red);}

And that’s it! Now, in addition to everything it already did, running webpack-dev-server will (1) compile using PostCSS, (2) read from colors.js, and (3) set all colors in colors.js as global CSS variables.

Limitations

The one limitation is hot-reloading. That is, hot reloading works perfectly on changes to JavaScript files and CSS files, with one exception: colors.js. Since colors.js is read on build, we need to restart the webpack dev server anytime we change or add a color variable. This question poses effectively the same issue (“…every time I change a variable I have to restart the webpack dev server”). For now, that’s a tradeoff I can live with.

Parting thoughts

This new pattern enables much more inline styling with JavaScript. That is, now our React components and D3 visualizations can, in theory, read style variables from JavaScript and never know about CSS.

Following this to its extreme of no-CSS/all-JS may seem crazy, but I remain curious. A lothas beensaid about how inline styles with JavaScript may be the future. At a minimum, it’s convenient and fun to do more JS and less CSS. I’m excited to see how the community experiments with inline styling and if there come to be best practices around separation of concerns.