Custom Cross-Browser CSS Properties

Cross-browser support for custom CSS properties is possible you know

Published on Apr 8, 2015

If you've been developing websites for a while you may have wondered if there is anyway to set custom properties in your CSS code and have some piece of JavaScript code read and make use of the data. This isn't supported by browsers as of now, but if it where then imagine what we could do with our application?

By having custom CSS properties, our JavaScript code could take over from where CSS code reaches a limitation. We could delegate all of the UI-related customization into our stylesheets and make use of media queries without having to have another set of customizations within our JavaScript code. Oh man, if only this was possible.

Well, using some clever CSS hackery and a custom build script, we can make use of custom CSS properties within all modern browsers. Let's explore how to make use of this ground-breaking new feature and see how it all works behind the scenes.

1.0 Custom CSS Properties

There's a new fantastic library out on the web called custom-props (by yearofmoo) which fills the gap for enabling support for browsers to read custom CSS properties that are defined within a CSS stylesheet. For this to work, the tool compiles the provided CSS code and renders it in such a way that all browsers can read the custom properties. Then the JavaScript library (custom-props.js) is used to examine the data defined in the compiled CSS code.

Let's imagine that we made a really cool new JavaScript plugin and you want to hook that into your application. Great it works. But now you want to add in some customization points which allow other developers to make use of your plugin in different ways. At first you may think that we could just use a JavaScript object in our code and configure things that way, but what if we could use CSS instead?

.super-cool-plugin {
  background:red;
  color:white;
  /* how the heck can we read these properties in JS??? */
  --custom-prop: 'value';
  --custom-prop-two: 'value2';
}

makes it work in all modern browsers. Let's go over how we can use it in the next section.

to top

2.0 Installation & Usage

The custom-props library consists of two components: a build script and a JavaScript library file.

The build script will parse and compile the provided CSS code that contains custom styles and the JavaScript library will then make sure that each of the big 5 browsers can understand it. The tool itself can be installed and used via gulp or grunt:

2.1 Gulp Usage

In order to get custom-props to work with Gulp we'll need to install the following NPM packages first:

npm install -g gulp
npm install gulp
npm install gulp-custom-props
var gulp = require('gulp');
var customProps = require('gulp-custom-props');
gulp.task('compile-css', function() {
  return gulp.src('./my-file.css')
    .pipe(customProps())
    .dest('./dist');
});

Now when the gulp compile-css command is run then our CSS file will be compiled and saved as dist/my-file.css.

2.2 Grunt Usage

In order to get custom-props to work with Grunt we'll need to install the following NPM packages first:

npm install -g grunt grunt-cli
npm install grunt
npm install grunt-custom-props
module.exports = function(grunt) {
  grunt.initConfig({
    customProps: {
      package: {
        src: [
          './my-file.css'
        ],
        dest: './dist/my-file.css'
      }
    }
  });
  grunt.loadNpmTasks('grunt-custom-props');
  grunt.registerTask('compile-css', ['customProps:package']);
};

Now when the grunt compile-css command is run then our CSS file will be compiled and saved as dist/my-file.css.

Step one is now done. Let's continue to step two where we can make use of these properties in our front-end JavaScript code.

to top

3.0 Reading the properties in JavaScript

Once the CSS code has been compiled into dist/my-file.css we can include that file into our website and start reading the properties using JavaScript. However, before we can do that we must include the custom-props.js plugin file into our website as well.

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="./dist/my-file.css" />
<script type="text/javascript" src="./custom-props.min.js"></script>
</head>
<body>
</body>
</html>

of the plugin file via NPM:

# copy over node_modules/custom-props/dist/custom-props.min.js
npm install custom-props
# copy over bower_components/custom-props/custom-props.min.js
bower install custom-props

the CSS variables that were compiled into our dist/my-file.css file.

<html>
<head>
<link rel="stylesheet" type="text/css" href="./dist/my-file.css" />
<script type="text/javascript" src="custom-props.min.js"></script>
</head>
<body class="super-cool-plugin">
<script type="text/javascript">
// we can also use the jQuery() load method
window.onload = function() {
  // the body element is where we defined the custom CSS
  // properties in the example above remember?
  var bodyElm = document.body;
  var customProp = CustomProps.read(bodyElm, 'custom-prop');
  alert(customProp); // "value";
  var customProp2 = CustomProps.read(bodyElm, 'custom-prop-two');
  alert(customProp2); // "value2";
  var allProps = CustomProps.data(bodyElm);
  alert(Object.keys(allProps)); // { "custom-prop":"value", "custom-prop-two":"value2" }
};
</script>
</body>
</html>

And there you have it. We can now fully read and make use of custom CSS properties that have been defined in a stylesheet in a galaxy far far away.

to top

4.0 How it works

So how exactly does it work? How can we possibly emulate such a powerful feature and not have any drawbacks on the current collection of browsers that we have today?

The way this works is via the CSS content property. This property is used as apart of pseudo-elements in CSS (you know the imaginary elements that are present when the :before and :after CSS classes are used), but it isn't used at all on a regular CSS selector, yet it is still detectable on the JavaScript side as a property. Do you see what we missed here? The content property is detectable on any element, yet it has no visual side-effect when used. This means that whatever data we dump into the content property can be accessed via JavaScript.

<style>
body {
  /* this will not be visible on the element */
  content:"custom data";
}
</style>
<script>
var customData = window.getComputedStyle(document.body).content;
</script>

But hold on for a second. This doesn't work just yet. IE doesn't allow us to read the content property just like that. All is not lost though, if we make a copy of the content property data and prefix it with a slash then IE can access and read that data via the element.currentStyle lookup object.

<style>
body {
  content:"custom data";
  -content:"custom data";
}
</style>
<script>
// Chrome, Opera, Safari, Firefox
var customData = window.getComputedStyle(document.body).content;
// IE
var customDataIE = document.body.currentStyle['-content'];
</script>

Well this works, but it's pretty ugly. Now you can see why a build tool exists. The build tool above does the conversion of the properties to a content object for us. So when we code something like this:

body {
  --custom-background-color: 'red';
  --custom-element-shape: 'triangle';
}

It turns into this:

body {
  --custom-background-color: 'red';
  --custom-element-shape: 'triangle';
  content:'{"custom-background-color":"red","custom-element-shape":"triangle"}';
  -content:'{"custom-background-color":"red","custom-element-shape":"triangle"}';
}

But hold on ... why do we not strip out the custom properties and just leave the content properties? The reason we leave the custom properties intact so that browsers that already support custom CSS properties can read them directly without having to parse the content JSON code. (Firefox is the only browser as of now that can do this.)

Now it makes sense we have a library for this. The JavaScript code inside of custom-props.js makes things much easier since the lookup code figures out how to fetch the content data based on the whether the browser is IE or something else. It also does some clever caching to avoid having to call getComputedStyle and parse JSON code each time a value is looked up on the an element that was previously queried and it also provides a natural lookup mechanism for Firefox (since it supports it).

to top

5.0 Is this experimental?

Not exactly. This feature worked a year ago when I found out about it. And only now with the recent developments in Angular2 did such a need for using it arise. The only drawback of this feature is that anything below Opera 15 manages to display the contents of the content CSS property on the element as if it were a pseudo element. Opera 12 was released four years ago and the usage of Opera overall is less than 1%. So we should be OK. In the worst case a special CSS selector can be made to override the content property for pre-15 Opera browsers.

This code has been tested on a fair number of browsers (via browershots and browser stack). No bugs so far and the compiled CSS code with the duplicate content properties is fully valid when it comes to CSS validation.

So we're all set to start creating some wikid cool plugins and websites with some awesome CSS customization features.

5.1 It has been attempted before

Other JavaScript libraries have attempted to get this feature to work, but there have been limitations. The problem with the other approaches is that they have to side-step the limitations of CORS and parse raw CSS to figure out where the properties live. This approach uses CSS detection naturally via getComputedStyle and element.currentStyle and anything involving CORS or security doesn't get in the way. Please consider this when trying to decide on what tool to use.

Any use of media queries and the cascade is most likely non-existant in other plguins. Since custom-props uses natural CSS DOM detection mechanisms we can ensure that the laws of CSS specificity are respected.

to top

6.0 Demos and Ideas

Alright so the intellectual foundation is in place. We understand how to use the tool and exactly what is going on behind the scenes when the compilation is going on and how the reading of the custom properties happens. Now all we need to do is figure out a good usecase or two so we can put this amazing new feature to use.

6.1 Desktop / Modile Interaction

What makes CSS great is that we different selectors and styles can be declated based on the platform, orientation and resolution state of the browser using media queries.

Let's say for example we have a rich text editor in our application and let's imagine that the editor is visible in a different state depending on if a mobile browser is used or not. By using media queries we can conditionally apply custom CSS properties depending on the state of the browser and device like so:

@media (max-width: 600px) {
  .editor-frame {
    --show-controls: false;
  }
}
@media (min-width: 601px) {
  .editor-frame {
    --show-controls: true;
  }
}

6.2 Beyond Animations

Using custom properties we can do some crazy stuff when it comes to animations. Here for example we're instructing our JavaScript code to run a series of CSS classes to dictate the sequence of animations.

.chained-animation {
transition:0.5s linear all;
  --animation-steps: ".one, .two, .three, .four";
}
.one { background: red; }
.two { background: blue; }
.three { background: green; }
.four { background: orange; }

Click here to view this demo in action

to top

7.0 Help Please

Even though this library is fairly small, there may be some issues with the grunt and gulp task files. So if you find any issues or want to contribute in anyway then please send me an email (which is listed on the contact page of this website) or post an issue directly in the github issue tracker page for custom-props.

Otherwise, please share and extend your love on Twitter and spread the word! Hopefully this amazing new feature boosts the capabilities of plugins, applications and websites all over the internet!

to top

Up Next