The Ultimate Guide to CSS Houdini
Tags: css, houdini, javascript
Published: 2021-12-08 15:31:59

## A complete guide of the Houdini APIs, example of usage and its browser support

> What is Houdini anyway? 😠
> ***Me, like two year ago***

_I learnt a lot about Houdini last year, and I'm amazed by all the possibilities it offers. I'm convinced Houdini is the future of CSS, and it was time for me to write the **ultimate guide** for it!_

Houdini is a **collection of browser APIs** that allows JavaScript to interact with the *CSS rendering engine*.

Quite exciting!It allows us to create *complex layouts*, *[custom and programmable backgrounds](https://paintlets.herokuapp.com/)*, *advanced animations* and way more.

In this post, we'll walk through each API of the specification and examine the compatibility.

There are *two groups of APIs*:

* **Low-level APIs** that are the building blocks for the high-level APIs. These are the Type Object Model API, the CSS Properties & Values API, Font Metrics API and Worklets.
* **High-level APIs** interact with the CSS rendering engine. They consist of the Paint, the Layout, and Animation API.

Ready? Let's start!!

## Low-level APIs

![Houdini Low Level APIs: Types Object Model API, CSS Properties & Values API, Font Metrics API and Worklets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqnhxcjyplaxg00fq4k4.png)

### Type Object Model API

Interacting with CSS properties using JS can be painful, especially when using *units*.

You'll have to work with a string containing the whole CSS value, something looking like `120px` or `2.4rem`.

Type **Object Model API** exposes these values as a JS object:

```js
{
  value: 2.4, 
  unit: "rem"
}
```

*Much better to work with!*

Our DOM elements now got a `computedStyleMap` method to work with the non-inline style and the `attributeStyleMap` attribute to work with inline styles.

<aside> 

⚠️ Careful, while `attributeStyleMap` is an attribute, `computedStyleMap` is a method (a method is a function in an object) and *needs to be called* before we can access anything.

</aside>

Here is how we use these properties:

```js
// Set and get an inline style
element.attributeStyleMap.set("width", CSS.rem(48))
element.attributeStyleMap.get("width")
// => {value: 48, unit: "rem"}

// Set and get computed style (note the "()" after computedStyleMap)
element.computedStyleMap().set("height", CSS.em(12))
element.computedStyleMap().get("height")
// => {value: 12, unit: "em"}
```

*In October 2021 this is supported in every browser except Firefox and Safari.*

### CSS Properties and Values API

The [CSS Properties and Values API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Properties_and_Values_API) allows us to define CSS Custom Properties (aka CSS variables) in a more precise way.

We can now define a *type*, an *initial value* and its *inheritance behavior*.

To define a property, we use `registerProperty` as such:

```js
CSS.registerProperty({ 
  name: "--brandingColor",
  syntax: "<color>", 
  inherits: false,
  initialValue: "goldenrod",
});
```

We'll be able to define it in the CSS in the future:

```css
@property --brandingColor{ 
  syntax: "<color>"; 
  inherits: false; 
  initial-value: goldenrod;
}
```

The `syntax` property represents the **type of the value**. It accepts: `<number>`, `<percentage>`, `<length-percentage>`, `<color>`, `<image>`, `<url>`, `<integer>` and `<angle>`. There is more on [the W3C specification](https://drafts.css-houdini.org/css-properties-values-api/#supported-names).

Setting the `syntax` helps the browser knowing how to transition between values.

In CSS, you can transition between colors but cannot between gradients.

Here, by defining `--brandingColor` we can, for example, animate a gradient 😎

That is how we proceed:

```css
.element{
	--brandingColor: goldenrod;
	background: linear-gradient(90deg, khaki 0%, var(--brandingColor) 100%);
  transition: --brandingColor 800ms ease-in-out;
}

.element:hover{
	--brandingColor: gold;
}
```

The hover animation will only work if the `--brandingColor` property type is `<color>`.

If your browser supports this API, the block should animate on this demo:

{% codepen https://codepen.io/tomquinonero/pen/JjJzeVe %}

*In October 2021 this is supported in every browser except Firefox and Safari.*

### Font Metrics API

Font Metrics API aims to give developers dimensions of text elements. It is really complex and hacky to do this right now, so this will solve a lot.

*Unfortunately*, this interface is still in its *early stage* and is **not supported in any browser yet**.

### Worklets

Worklets are *scripts* that plugs to *low-level parts of the rendering engine*. It runs JavaScript and [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) code.

Houdini introduces three Worklets: the *Pain Worklet*, the *Layout Worklet* and the *Animation Worklet* that are used to power our *high-level APIs*.

## High-level APIs

### Paint API

![Houdini CSS: The Paint API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64wwsmkodr3p47fsu8f2.png)

Paint API let us use the [2D Rendering Context](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) to draw backgrounds, text, and borders. We can draw using JS function, and we can use CSS Variables as parameters for this function. 

**To use the Paint API:**

1. Register the Paint Worklet
2. Add it as a module
3. Call it with `paint()` in your CSS

The Paint Worklet code needs its own JS file. 
This is how you register it:

```js
// cornerbox.js

class CornerBox{
  paint(ctx, geom, properties) {
	  // The actual painting happens there
  }
}

// Register our class under a specific name
registerPaint('cornerbox', CornerBox);
```

Then, we need to add it as a module where we put our JavaScript. We also declare the property we might want to animate using the *CSS Property and Value API*:

```js
//main.js

// We register the property we want to animate
CSS.registerProperty({ 
  name: "--cornerbox-length",
  syntax: "<length>", 
  inherits: false,
  initialValue: "120px",
});

CSS.registerProperty({ 
  name: "--cornerbox-width",
  syntax: "<length>", 
  inherits: false,
  initialValue: "16px",
});

// Add the module from a local file
CSS.paintWorklet.addModule("./cornerbox.js");
// Or add it from a CDN
CSS.paintWorklet.addModule("https://unpkg.com/cornerbox@0.0.3/CornerBox.js");
```

We can now use `paint(cornerbox)` in our CSS:

```css
.element {
  width: 20rem;
  height: 20rem;
  --cornerbox-color: #5f64e2;
  --cornerbox-length: 120px;
  --cornerbox-width: 16px;
  background: paint(cornerbox);
  transition: --cornerbox-length 400ms ease-in-out, 
		--cornerbox-width 400ms ease-in-out;
}

.element:hover{
  --cornerbox-length: 220px;
  --cornerbox-width: 24px;
}
```

We use `--cornerbox-length`, `--corner-width` and `--corner-color` to configure our corner-box.

Have a look at the demo below to see it in action 🤙

{% codepen https://codepen.io/tomquinonero/pen/eYRXbRL %}

*This wraps up the Paint API section.*

Paint Worklets is what hooked me to Houdini in the first place! 

*There are endless applications to this 🤗✨*

You should definitely check out [Houdini.how](http://Houdini.how)! It's a collection of Paint Worklets that are ready to use. This is where [the Corner Box worklet](https://houdini.how/#demo-cornerbox) comes from.

*In October 2021 this is supported in every browser except Firefox and Safari.*

### Layout API

![Houdini CSS: The Layout API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqxzmjnefh4wfwtodg0x.png)

The Layout API allows us to define new layout modes that can be used as a `display` property in our CSS. 

It opens up a lot of possibilities! But this is a complex one and the specification is not definitive yet. 

For more information, [have a look at the specification on W3.org](https://www.w3.org/TR/css-layout-api-1/). 

We'll show a working example, but *not dive too deep* into the code. 

In this section, we'll use the [Google Chrome Lab's Masonry Worklet](https://github.com/GoogleChromeLabs/houdini-samples/blob/master/layout-worklet/masonry/masonry.js).

That is what a Layout Worklet look like (the logic has been removed here):

```js
// masonry.js

registerLayout('masonry', class {
  static get inputProperties() {
    return [ '--padding', '--columns' ];
  }

	static get inputProperties() {
    return [ '--order' ];
  }

  async intrinsicSizes() {}
  async layout(children, edges, constraints, styleMap, breakToken) {
    // The actual code happens there
  }
});
```

Like a Paint Worklet, let's add it as a module:

```js
// main.js 

// local
CSS.layoutWorklet.addModule("./masonry.js");
// elsewhere
CSS.layoutWorklet.addModule("https://raw.githubusercontent.com/GoogleChromeLabs/houdini-samples/master/layout-worklet/masonry/masonry.js");
```

And use it in our CSS:

```css
.element{
	--padding: 20;
  --columns: 2;
	display: layout(masonry);
}
```

*And… We got a masonry layout working!*

Have a look at the demo:

{% codepen https://codepen.io/tomquinonero/pen/vYZPbxJ %}

This is exciting, but not quite ready for now. It is *not documented on MDN yet*, and the implementation will likely change in the future. 

**Let's wait a few years on that one!**

*In October 2021 this feature is hidden behind a flag (Experimental Web Platform features) in every browser except Firefox and Safari.*

### Animation API

![Houdini CSS: The Animation API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v89azr5pnlmwo9drtad.png)

Animation API allows us to make advanced animations!

It aims to provide developers with a more performant way of animating using CSS.

Let's register our Animation Worklet:

```jsx
//superBounce.js

registerAnimator("superBounce", class {
  constructor(options) {
    // Our code goes here
  }
  animate(currentTime, effect) {
    // Our code goes here
  }
});
```

And add it as a module:

```jsx
// main.js

CSS.animationWorklet.addModule("./superBounce.js");
```

To use an Animation Worklet we need to declare what we would normally declare in a `@keyframes` in JavaScript.

Let's put side to side what we would do with `keyframes` and what we would do using JavaScript:

```css
// Using the CSS approach

.element{
	animation: bounce 800ms ease-in-out infinite;
}

@keyframes bounce {
  0% {
    transform: scale(1);
  }
  25% {
    transform: scale(1.1);
  }
  50% {
    transform: scale(1);
  }
  75% {
    transform: scale(1.15);
  }
}
```

```jsx
// The JavaScript approach

const keyframes = [{
    transform: 'scale(1)',
    offset: 0
  },
  {
    transform: 'scale(1.1)',
    offset: 0.25
  },
  {
    transform: 'scale(1)',
    offset: 0.50
  },
  {
    transform: 'scale(1.15)',
    offset: 0.75
  },
]

const timing = {
  duration: 800,
  easing: "ease-in-out",
  iterations: Infinity
}

const element = document.querySelector('.element--js')
element.animate(keyframes, timing)
```

Using JavaScript, we can do a bit more than we CSS. For example, we can define the `easing` in each keyframe. 

Also, we can bind the animation progress to Scroll Events, play and pause it at will, change the playback rate, reverse the animation etc…

Here is a demo on CodePen:

{% codepen https://codepen.io/tomquinonero/pen/WNOWvMP %}

That's it, we learned how to make custom animation using the Web Animation API 😎

For a more in-depth read, read the [MDN guide about Web Animation API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API).

*In October 2021 this feature is hidden behind a flag (Experimental Web Platform features) in every browser except Firefox and Safari.*

## Conclusion

I am personally *very much* *excited* about all these new features, especially the Paint API!!

I would love to see in the future a lot of available layout and paint worklets that we could customize using CSS variables. 
That'll be an outstanding new step for CSS 😍

***I'm Tom Quinonero, I write about design systems and CSS, [Follow me on Twitter](https://twitter.com/tomquinonero) for more tips and resources 🤙***

## Sources and links

- [CSS Houdini Playground](https://codepen.io/collection/dbPEjg): A CodePen collection I made to illustrate this post
- [A Practical Overview Of CSS Houdini](https://www.smashingmagazine.com/2020/03/practical-overview-css-houdini/) by [Adrian Bece](https://twitter.com/AdrianBeceDev): An exhaustive and clear guide on Houdini. Probably the best thing to read if you want to go deeper!
- [Paintlets](https://paintlets.herokuapp.com/): A gallery of tweakable and downloadable paint worklets (Chrome only)
- [Houdini.how](https://houdini.how/): A library of Worklet, mostly Paint worklets
- [Houdini Spellbook](http://houdini.glitch.me/): A nice introduction guide to Houdini
- [Houdini Samples](https://github.com/GoogleChromeLabs/houdini-samples): A GitHub repository with nice worklets examples
- [Generating Knockout Text with the CSS Paint](https://dev.to/jamessouth/generating-knockout-text-with-the-css-paint-houdini-api-2lac) by [James South](https://twitter.com/dsouthbot)
- [Houdini on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Houdini)