This post was originally published on Octuweb in October 2018.
For many people CSS is a magician’s thing, some might even believe it’s witchcraft, but that’s because they don’t know the necessary spells to have real control over CSS.
CSS Houdini is like the magic book that takes us to the next level in our magic control, a series of new spells to control the different phases in the rendering process.
.CSS
Let’s start by understanding the current scenario.

In the diagram we can see the rendering process the browser does, converting HTML into the pixels that end up represented on the device.
Once the process starts we have no control, in any of the phases, over how the browser parses HTML and CSS and converts it into the DOM and CSSOM object models respectively.
If we want to modify the behavior of any CSS property, whether to fix a bug or to adapt the behavior to our content and design, we have to do it with JavaScript once we have the DOM built, select the element to apply the new CSS properties and generate a new CSSOM.

In this other diagram we can see that with JavaScript we can modify the DOM and/or CSSOM, triggering again the Cascade, Layout, Paint and Composite process. This represents an additional cost to the browser, and although it may seem like a totally acceptable cost on our fancy development machine, we must keep in mind that most content consumption comes from mobile devices, where we should pay special attention to minimizing performance impact.
.CSS-Houdini
The objective of the CSS-TAG Houdini Task Force (CSS Houdini) is to jointly develop features that explain the “magic” of Styling and Layout on the web. - CSS Houdini
CSS Houdini is a collection of new APIs that gives us (partial) access to the browser’s CSS engine.

Here we can see how things change, proposing an API to access each of the phases of the rendering process (the gray boxes indicate planned specifications, but nothing is defined yet).
Let’s see what APIs we have “available” currently.
- Paint API: this API offers us a (limited) canvas to draw in
backgroundandborderproperties. - Typed OM API: handles object-based values for working with CSS values in JavaScript.
- Properties & Values API: provides an API to define CSS properties and give them a type, behavior and default value. Very useful to use together with other Houdini APIs.
- Animation Worklet: comes with the proposal to improve user experience by separating scroll and mouse-related interactions from the main thread. So we’ll have smoother animations, transitions and interactions.
- Layout API: Build your own layouts, reinvent flexbox and grid and implement everything with CSS properties.
- Parser API: exposes the CSS parser, this allows us to parse arbitrary CSS-like languages into a slightly typed representation.
- Font Metrics API: exposes some font data in a compact form that gives you much more control over text in your application.
This allows us to extend CSS through JavaScript.
.Extending-CSS
The best way to understand the potential is with examples, so let’s start seeing how to work with some of the new APIs.
For the examples we’re going to use Google Chrome Canary and the Experimental Web Platform features flag activated (chrome://flags/#enable-experimental-web-platform-features), as it’s the browser with the most support for the new APIs, as we’ll see later.
& .Worklets
A worklet is basically what we’re going to use to connect with the browser’s CSS engine. It’s a JavaScript module where we’ll define the “magic” we can use in CSS. We have no control over worklet execution, the rendering engine is what makes the calls when necessary.
The most interesting point of a worklet is that it has no performance impact, because each worklet runs within its own execution thread. The power of JavaScript with the smoothness of CSS.

To add a worklet module we’ll use the addModule method.
<script>
if ("paintWorklet" in CSS) {
CSS.paintWorklet.addModule("PlaceholderBoxPainter.js");
}
</script>
In this case we check that we have the Paint API available with the conditional if ('paintWorklet' in CSS), this would allow us to present an alternative and not load the module if it’s not supported.
This would be the content of the PlaceholderBoxPainter.js module
class PlaceholderBoxPainter {
paint(ctx, size) {
// Magic 🎩
}
}
registerPaint("placeholder-box", PlaceholderBoxPainter);
Don’t worry if you don’t understand something, with the Paint API example the pieces will fit together.
& .Paint-API
This has been the first API to reach the stable version of Chrome (version 65, March 2018).
In the previous point we saw how to load a worklet module, now let’s see an example of the Paint API.
The first thing we’re going to do is load our worklet, if it’s supported by the browser.
<script>
if ("paintWorklet" in CSS) {
CSS.paintWorklet.addModule("PlaceholderBoxPainter.js");
} else {
document.querySelector("html").classList.add("no-support");
// Here we add a class to <html> to show a message
// or the alternative as fallback
}
</script>
<div class="placeholder"></div>
This is the content of our PlaceholderBoxPainter module.
class PlaceholderBoxPainter {
paint(ctx, size) {
ctx.lineWidth = 2;
ctx.strokeColor = "#FC5D5F";
// Draw a line from top left
// to bottom right.
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(size.width, size.height);
ctx.stroke();
// Draw a line from top right
// to bottom left
ctx.beginPath();
ctx.moveTo(size.width, 0);
ctx.lineTo(0, size.height);
ctx.stroke();
}
}
registerPaint("placeholder-box", PlaceholderBoxPainter);
The first detail we can see is that we’re defining a class, and we won’t need a transpiler like Babel, since the browser interprets it natively.
In the example we have a single method, paint() which is what will be executed every time the CSS engine needs to repaint the element. We have two initial parameters, ctx the context where we can use a limited version of the CanvasRenderingContext2D object (we can’t draw text or work with bitmaps) and the second parameter is size where we have the element’s width and height.
As indicated in the code comments, our new worklet will paint an X, as is usually used as a placeholder in mockups to reference an image.
We’ve defined ctx.lineWidth = 2 and ctx.strokeColor = '#FC5D5F' as default values for thickness and color.
Finally, with registerPaint('placeholder-box', PlaceholderBoxPainter) we’re registering the module, where we pass as first parameter the name we’ll use in CSS and the name of the class we just defined.
Let’s see how the CSS looks.
.placeholder {
background-image: paint(placeholder-box);
...;
}
This is where we see a new function, paint(placeholder-box) with the parameter we used in the module registration.
Like any CSS property, we can define a fallback by adding a previous line.
.placeholder {
background-image: linear-gradient(to bottom, #fc5d5f 0%, #a53d3d 100%);
background-image: paint(placeholder-box);
...;
}
Here you can see the example Placeholder Box
&-CustomProperties
Let’s make our module more dynamic by adding Custom Properties to our class.
CSS
.placeholder {
--line-with: 2;
--stroke-color: #fc5d5f;
background-image: linear-gradient(to bottom, #fc5d5f 0%, #a53d3d 100%);
background-image: paint(placeholder-box);
...;
}
Here we’ve added two new CSS properties and assigned them a value. You’ll have seen that they’re not properties you recognize, don’t worry we’ll talk about them in a moment.
Worklet
class PlaceholderBoxPainter {
static get inputProperties() {
return ["--line-with", "--stroke-color"];
}
paint(ctx, size, properties) {
ctx.lineWidth = properties.get("--line-with");
ctx.strokeColor = properties.get("--stroke-color");
// Draw a line from top left
// to bottom right.
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(size.width, size.height);
ctx.stroke();
// Draw a line from top right
// to bottom left
ctx.beginPath();
ctx.moveTo(size.width, 0);
ctx.lineTo(0, size.height);
ctx.stroke();
}
}
registerPaint("placeholder-box", PlaceholderBoxPainter);
As you’ll have seen, we’ve added several lines of code to our worklet module.
On one hand we’ve added a static method to get the properties we have defined in the CSS declaration context. Here we define the properties we want to use in our worklet, in this case --line-with and --stroke-color.
static get inputProperties() {
return [
'--line-with',
'--stroke-color'
];
}
We’ve also changed the line thickness and color assignment.
ctx.lineWidth = properties.get("--line-with");
ctx.strokeColor = properties.get("--stroke-color");
JS
CSS.registerProperty({
name: "--line-with",
syntax: "<number>",
inherits: false,
initialValue: 1,
});
CSS.registerProperty({
name: "--stroke-color",
syntax: "<color>",
inherits: true,
initialValue: "rebeccapurple",
});
Here another of the new APIs comes into play, the Properties & Values API (operational in the Canary version of Google Chrome). With which we can register our own CSS properties. I think this API is amazing, let’s analyze the object we’re passing as argument.
namewith which we define the CSS property name.syntaxallows us to define the “type” of accepted data, in case it’s not the defined one it ignores it.inheritswith a boolean value we indicate whether it accepts inheritance from parent or not.initialValuethe initial value of the property.
By defining these properties, we can use them as if they were variables to have them available in our worklet module.
You can see the changes here
.Support
I know this part is what discourages many people, but like any advance in web development we have an implementation period in browsers. Surma maintains the page Is Houdini ready yet‽, where he presents a table where we can see API support in different browsers.

Yes, everything is still very green, well very red 😅.
The only API with Candidate Recommendation at the W3C is the Paint API, which is already available in stable versions of Chrome and Opera, as is the Type OM API, even with a Working Draft status at the W3C. Activating experimental flags in Canary, we can start playing with Layout API, Properties & Values API and Animation Worklet.
.Conclusion
I’ve read several articles defining CSS Houdini as the solution for developing polyfills for CSS or as the Babel for CSS (which I think is a totally wrong approach).
You might be wondering, as Naiara, Diana or Ángel surely do 😉, if we’ll be able to define custom behaviors that aren’t defined in the W3C specification, isn’t that moving away from the philosophy of web standards?
IMHO, I think with this proposal, they’re giving us access to new APIs that offer us more “power” and control. And like any new technology it will have an acceptance period where we’ll see examples of not very consensual implementation… but for now, let’s enjoy the option of improving our magic with these new spells.
.References
I’ll leave you a list of resources where you’ll find links to documentation, articles, videos and most importantly: examples where you can analyze behavior (remember to use Chrome Canary with chrome://flags/#enable-experimental-web-platform-features activated).