Descartes
Styling for Programmers
An experimental library for writing CSS in JavaScript
Get started See FeaturesAn experimental library for writing CSS in JavaScript
Get started See FeaturesI'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!
<!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!
}
}
})
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.
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;
}
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!
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.
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.
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
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.
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
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;">
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);
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);
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);
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));
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.
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.
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.