Descartes

Styling for Programmers

An experimental library for writing CSS in JavaScript

Get started See Features

Share on Twitter and Facebook

Smart Stylesheets

Powered by JavaScript

I've taken some ideas from CSS preprocessors like Less and Sass and wondered what it would be like to write CSS as JSON. You can get all the advantages of a typical preprocessor and some: complex programming to set styles, access to the DOM, and even basic event listeners!

Click here to see it in action

index.html
<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="descartes.js"></script>
  </head>
  <body>
    <h1>Hello World!</h1>
  <script type="text/javascript" src="styles.js"></script>
  </body>
</html>
styles.js
var reset = {"margin": 0,
  "padding": 0,
  "box-sizing": "border-box"
};
var rand_angle = function() {
  return Math.round(Math.random() * (180) - 90);
};
var rand_rgba = function() {
  return "rgba("+[255,255,255].map(function(x) {
    return Math.round(Math.random() * x);
  }).join()+", 1)"
};

new Descartes({ // Just put descartes.js in the <head> tag
  "html": {
    "_mixins": reset, // Reuse CSS rules with mixins
    "body": { // Nest selectors just like in Sass and Less
      "_mixins": reset,
      "_listeners": [
        [window, "click"],
        [window, "touchstart"]], // Bind events...
      "background": function() {
        return 'linear-gradient('
          + rand_angle().toString() + 'deg,'
          + rand_rgba() + ','
          + rand_rgba() + ')'
      } // ...to set dynamic property values when they fire!
    }
  }
})
 

Small Library

Descartes is just over 30KB when minified and just over 300 lines of code. It's written entirely in JavaScript with its one dependency, Sizzle, baked in. It works really well with some of your favorite libraries, and especially jQuery.

See the source code

 
Descartes + JavaScript
new Descartes({
  "html": {
    "margin": 0,
    "padding": 0,
    "body": {
      "margin": 0,
      "padding": 0,
      "section": {
        "max-width": "800px"
      }
    }
  }
})
Sass or Less Equivalent
html {
  margin: 0;
  padding: 0;
  body {
    margin: 0;
    padding: 0;
    section {
      max-width: 800px;
    }
  }
}
Plain CSS Equivalent
html {
  margin: 0;
  padding: 0;
}
html body {
  margin: 0;
  padding: 0;
}
html body section {
  max-width: 800px;
}

A Familiar Style

JavaScript like Less and Sass

It turns out writing CSS as JSON isn't all that different from Less and Sass. I've written Descartes to act just like other CSS processors with some extra features. You get nesting, mixins, and variables just like the others!

Learn the basics

 

Pretty Fast

Descartes uses an object literal and JavaScript to calculate the cascade and inline styles on elements. It's still reasonably fast on some page loads: the styles for this page took milliseconds to render on this page load.

Reload to test speed again

 

Open Source

Help me fix some things

Descartes is highly experimental and very much a work in progress. Members of the developer community (and even you) have thought a lot about many of these problems, and I want to build on that past knowledge. Take a look at open issues, write some extensions, and have discussions about what's presented here.

Go to GitHub

 

Known Issues

Improve performance

Since it's all JavaScript, we don't get browser optimizations for CSS. There are a lot of people that are better at JavaScript performance that could help out here.

Helper functions and variables

Some functions and variables are used all the time, like grids and colors. Should these be settings and functions part of the core library, or be an extension or companion library?

Compatability with other libraries

Some JS libraries work really well with Descartes like jQuery. But what about heavier front end frameworks like React, Angular, and Backbone?

Generate backup CSS stylesheet

Descartes should be able to work just like any other preprocessor and generate CSS that can be used as a backup. This just needs to get built.

 

See more on GitHub or open up an issue

Features

Descartes has most of the features you're familiar with from Less and Sass, but adds the power of JavaScript to your styles. Get an overview of what the library has to offer.

Style Tree

You write all of your styles into a single JavaScript object literal that you pass to Descartes. It feels just like writing in other CSS preprocessors like Less or Sass, but you get all the benefits of constructing your style tree with JavaScript. That means real variables, functions, and operations.

var standardSize = 16;
var tree = { // Just a JavaScript object literal
  "html": {
    "margin": 0, // Implicit conversion to px
    "padding": 0,
    "height": "100%",
    "body": { // Nested inside of `html`
      "color": "#333",
      "font-family": "Helvetica",
      "font-size": standardSize, // Use variables
      "h1": { // Nested inside of `html body`
        "font-size": standardSize * 1.2, // Operations
        "font-weight": 300
      }
    }
  }
};
var d = new Descartes(tree); // Renders style tree

Priority Cascading

Cascading in traditional CSS gets complex and unmanageable very fast. Descartes uses Priority Cascading, which bypasses stylesheets completely and inlines styles on elements according to depth in the style tree. When there are conflicts, the rules with the higher priority win out.

new Descartes({
  "html": { // Root of tree, so priority = 0
    "color": "red", // 0
    "h1": { // Nesting, so priority++
      "color": "white" // 1 (wins over red)
    }, // Finish nesting, so priority--
    "body": { // priority++
      "h1": { // priority++
        "color": "blue" // 2 (wins over red, white)
      } // priority--
    } // priority--
  }
});
// After cascading, you will get <h1 style="color: blue;">

Mixins

Sometimes you'll want to reuse some of the same rules over and over again and variables won't cut it. Descartes supports mixins: reusable sets of declarations that you can insert into your style tree. Just define some preset rules and pass them into the mixins property for that selector. They apply in order, and won't overwrite your explicit styles.

var _reset = { // Mixin objects usually prefixed with _
  "margin": 0,
  "padding": 0
};
var _font = function(n) { // Mixin function that returns an object
  var mixin = {
    "font-size": 16 * Math.pow(1.4, n), // Modular scale
    "font-family": "Helvetica",
    "font-weight": 600
  };
  return mixin;
};
var tree = {
  "html": {
    "_mixins": _reset, // Gets margin and padding!
    "background": "#fff",
    "h1": {
      "_mixins": _font(6) // You can pass function values
    },
    "h2": {
      "_mixins": _font(5),
      "font-weight": 300 // Explicit rules get priority
    },
    "p": {
      "_mixins": [_font(1), _reset] // Applied in order
    }
  }
};
var d = new Descartes(tree);

Dynamic Values

Because you're writing styles in JavaScript, you can pass functions to compute properties in Descartes. Each node is passed as the first argument to any functions you define, so you can create powerful declarations that weren't possible before. You even get access to the DOM, so you can generate values based on other element properties.

// Vertical alignment using margins
var tree = {
  .
  .
  .
  ".verticalAlign": {
    "margin-top": function(_) {
      // _ is the individual .verticalAlign element
      var parentHeight = _.parentNode.height; // Parent height (!)
      var height = _.height; // Get current node's height
      return (parentHeight + height)/2; // Compute margin in px
    }
  }
  .
  .
  .
};
var d = new Descartes(tree);

Event Listeners

Combining event listeners with dynamic values unlocks a whole new level of control over your styles. You can bind any event to recompute the rules for that selector. This makes responsive design, parallax, and basic interactivity a breeze.

// Visible nav bar based on scroll direction like on this page

var lastScroll = $(window).scrollTop(); // Use jQuery if you want!
var tree = {
  "nav": {
    "_listeners": [[window, 'scroll']], // Listen to scroll event
    "overflow": "hidden",
    "position": "fixed",
    "width": "100%",
    "transition": "all 0.5s ease", // Nice animations
    "height": function(_) { // Dynamic value
      var scroll = $(window).scrollTop();
      if (scroll > lastScroll) { // Detect scroll direction
        lastScroll = scroll;
        return 0; // Hide the nav
      }
      lastScroll = scroll;
      return 50; // Show the nav
    }
  }
};
var d = new Descartes(tree);

Transformers

Because you're just passing a JavaScript object literal into Descartes, you can extend and manipulate the style tree before it's rendered by the library. That means you can do things like add browser prefixes to CSS3 properties, add predefined rules like a grid, or even invent new rules that turn into traditional style declarations.

var tree = {
  ".animate": { // Uh oh, forgot browser prefixes!
    "animate": "all 0.5s ease"
  }
};
// This is a transformer that will add prefixes!
function prefixTransformer(tree) { // It takes a style tree
  var newTree = {}; // Store a temporary value
  for (var key in tree) {
    var value = tree[key]; // Get the property value
    if (typeof value === 'object' && !(value instanceof Array)) {
      // Recursively apply nested rules
      newTree[key] = prefixTransformer(value);
    } else {
      newTree[key] = value;
      if (key === "animate") { // See if it needs a prefix
        var prefixes = ["-webkit-"];
        for (var index in prefixes) {
          // Add browser prefixes to tree
          newTree[prefixes[index] + key] = value
        }
      }
    }
  }
  return newTree; // And "transforms" it with prefixes
}
// Apply the transformer before render
new Descartes(prefixTransformer(style));

About

Who I am and why I built this

My name is Jon Chan. I'm a developer and the head of Marketing Engineering and Developer Evangelism at Stack Overflow. I'm also the founder of Bento, where I help people around the world become self-taught full stack developers. I work a lot in the front end and think a lot about making development faster.

Follow me on Twitter


Why build Descartes?

At Stack Overflow and Bento, I work on a lot of highly interactive front end that uses a lot of JavaScript and CSS. I kept running into the same issue: the moment the front end reaches some complexity, the separation between JavaScript and CSS breaks down really quickly. Wrestling with the cascade has also always been a problem. I wondered what it might be like to break that barrier down entirely, against conventional wisdom.

 

Other Motivations

Bypass the Cascade

Even using preprocessors like Less and Sass don't fully solve the problems with the cascade which get really hairy at scale. Using JavaScript to compute and inline styles means we can get full control over the cascade at the cost of some speed.

Evolving Preprocessors

While Less and Sass give you some powerful tools like variables, mixins, and basic calculation, I always thought it could go a step further. I've tried to keep these familiar features, but introduce all the programming power of JavaScript: more programming control, DOM access, and event binding.

Inspiration

There are a lot of people that have had similar ideas that I'm building on. Take a look at what some others have said about these problems:

Cascading Shit Show by Jacob Thorton (@fat)

React: CSS in JS by Christopher "vjeux" Chedeau

AbsurdJS Hacking the Front End by Krasimir Tsonev (@KrasimirTsonev)

Fun + Experimentation

Working on this is just fun. Everyone that I talk to about writing CSS in JavaScript says it's really crazy, and chances are they're probably right, but it is a lot of fun trying. From when I first commited to the core to publishing this page, it took about a week, and I want to see where this will go. I'm curious to hear what other people's thoughts are.