The Holy Grail of reusable style frameworks is to provide ways to change colors easily. After all, sites need to match the brand identity. Exposing SASS variables like Bootstrap's $brand-color
, Foundation's $primary-color
, and Kendo UI's $accent
allow this. But what if scripts need the values of these variables?
We had this problem while developing Kendo UI for Angular. The styles of the charting package follow the theme design, with the same colors as defined in SCSS. Simply copying the color values in the chart scripts is duplication, and would make the charts look different if customers change the theme colors. So why not encode these variables in the CSS, and let the script read them from there? This creates a single source of truth for the colors - the SCSS code.
The data
Here is the data that we needed to pass from SCSS:
$color: #bada55;
$number: 10;
$size: 12px;
$font: Arial, Helvetica, sans-serif;
Ideally, this should be its representation in JSON:
{
"color": "#bada55",
"number": 10,
"size": "12px",
"font": "Arial, Helvetica, sans-serif"
}
A simple encoding
One way to tackle this problem is to have one selector per variable, following a pattern:
.--var-color { color: $color; }
.--var-number { margin-top: $number; }
.--var-size { font-size: $size; }
.--var-font { font-family: $font; }
And the values can be extracted through the following script:
// variables and their types
var variables = {
color: "color",
number: "margin-top",
size: "font-size",
font: "font-family"
};
// use computedStyle to get their values
var element = document.createElement("div");
document.body.appendChild(element);
var result = Object.keys(variables).reduce((obj, name) => {
element.className = `--var-${name}`;
var computed = window.getComputedStyle(element, null);
obj[name] = computed.getPropertyValue(variables[name]);
return obj;
}, {});
document.body.removeChild(element);
While inefficient, the above code shows that the approach is working, and works pretty good for a few variables.
Less code...
Writing out every CSS rule gets tedious and is prone to typos. Why not generate the CSS?
$exported: (
color: $color,
number: $number,
size: $size,
font: $font
);
@each $name, $value in $exported {
.--var-#{$name} {
font-family: "#{$value}";
}
}
Since we use the font-family
property to transfer the data, and it is always a string, it allows the script code to be simplified as well:
// variables, no need for types
var variables = [ "color", "margin-top", "font-size", "font-family" ];
// use computedStyle to get their values
var element = document.createElement("div");
document.body.appendChild(element);
var result = variables.reduce((obj, name) => {
element.className = `--var-${name}`;
obj[name] = window.getComputedStyle(element, null).fontFamily;
return obj;
}, {});
document.body.removeChild(element);
... and faster
All these DOM manipulations - calling getComputedStyle
, setting className
- can get costly for the application startup time. How can the DOM code be reduced? If there is a more efficient way of passing information, and the code uses a single element. Luckily, there is such a way -- through the content of pseudo elements.
$exported: (
color: $color,
number: $number,
size: $size,
font: $font
);
$json-list: ();
@each $name, $value in $exported {
$json-list: append($json-list,
unquote('"#{$name}": "#{$value}"'),
comma
);
}
#--variable-info::after {
content: '{ #{$json-list} }';
display: none;
}
The above allows us to simplify the script code to:
var element = document.createElement("div");
element.setAttribute("id", "--variable-info");
document.body.appendChild(element);
// get the generated content
var content = window.getComputedStyle(element, '::after').content;
// generated content is a quoted string, thus the need for two passes
var result = JSON.parse(JSON.parse(content));
document.body.removeChild(element);
And voila! JSON strings in CSS, read from scripts.
Applications
The above code has no dependencies, and is pretty straightforward to use. In Kendo UI, it simplifies the work of both the library developers and the library consumers, and makes switching themes a breeze. Other uses are perhaps theme customization tools that want to encode information about a theme in CSS.
Using this in your own projects? Let me know in the comments!