# Introducing css-typed-om, a complete and WPT-tested polyfill for CSS Typed OM

> Source: <https://www.bram.us/2026/10/13/introducing-css-typed-om-a-complete-and-wpt-tested-polyfill-for-css-typed-om/>
> Published: 2026-10-13 08:51:47+00:00

Being a big fan of the [CSS Typed OM](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Typed_OM_API), I’ve wanted a proper polyfill for it for years. Just before my summer vacation back in July, with the help of Gemini/Antigravity, I finally built one: [`css-typed-om`](https://github.com/bramus/css-typed-om-polyfill).

~

### [#](#the-problem) Why CSS Typed OM? (And why a polyfill?)

If you’ve ever read or manipulated styles in JavaScript using `element.style` or `window.getComputedStyle()`, you know the pain of dealing with strings. Want to read an element’s `width` and add `20px` to it? You have to parse the string, strip the unit, convert it to a number, do the math, and concatenate it back into a string. Throw `calc()`, `transform` matrices, or modern color spaces into the mix, and string manipulation quickly falls apart.

``` js
const styles = getComputedStyle($el);
const opacity = styles.getPropertyValue('opacity');

console.log(opacity); // '0.2'
console.log(typeof opacity); // String!?
```

That’s where the [CSS Typed Object Model (CSS Typed OM)](https://developer.chrome.com/docs/css-ui/cssom) comes in. Instead of strings, it exposes CSS values as typed JavaScript objects — such as `CSSUnitValue`, `CSSMathSum`, `CSSTransformValue`, and `CSSKeywordValue` — through `element.attributeStyleMap`, `element.computedStyleMap()`, and unit factory functions like `CSS.px(20)`.

``` js
const styles = $el.computedStyleMap();
const opacity = styles.get('opacity');

console.log(opacity); // CSSUnitValue
console.log(opacity.value); // 0.2
console.log(opacity.unit); // 'number
```

However, browser support isn’t 100% uniform yet. While Chromium and Safari ship CSS Typed OM, Firefox’s native implementation is still a work in progress behind the `layout.css.typed-om.enabled` flag (see [Firefox bug 1278697](https://bugzil.la/1278697)). And even in browsers that do support Typed OM, some newer parts of the spec — like the `CSSColorValue` subclasses (`CSSRGB`, `CSSOKLCH`, etc.) — aren’t available everywhere yet.

~

### [#](#enter-css-typed-om) Enter `css-typed-om`

Building a standalone CSS Typed OM polyfill has been sitting on my backlog for years — especially when seeing other projects like the *(partial)* [`scroll-timeline` polyfill](https://github.com/flackr/scroll-timeline) having to roll and bundle parts of the CSS Typed OM implementation themselves just to support unit values like `CSS.percent()` or `CSS.px()`.

However, the [CSS Typed OM specification](https://drafts.css-houdini.org/css-typed-om-1/) is *huge*, covering everything from a full CSS tokenizer and `calc()` expression simplifier to 2D/3D matrix math and WebIDL interface rules. Writing all of that by hand was simply too big of an undertaking for a side project. But now, thanks to AI, that long-standing goal finally came within reach: meet [`css-typed-om`](https://github.com/bramus/css-typed-om-polyfill). Guided by the specification and the official Web Platform Tests, `css-typed-om` is a complete, standalone, and full tested polyfill that you can drop into any project. 🚀

~

### [#](#installation-and-usage) Installation & Usage

You can install the package from npm:

```
npm install css-typed-om
```

Depending on your setup, you can use `css-typed-om` in two ways.

~

#### 1. As a Global Polyfill

Import the package once at the entry point of your application to automatically patch `window.CSS`, `Element.prototype.computedStyleMap`, `HTMLElement.prototype.attributeStyleMap`, and all CSS Typed OM classes:

``` js
import 'css-typed-om';

// Read computed styles as typed objects
const element = document.querySelector('.my-element');
const width = element.computedStyleMap().get('width');

console.log(width.value); // e.g., 100
console.log(width.unit);  // e.g., 'px'

// Set inline styles using CSS unit factories
element.attributeStyleMap.set('opacity', CSS.number(0.5));
element.attributeStyleMap.set('margin-top', CSS.px(20).add(CSS.em(2)));
```

💁♂️ Don’t use a bundler? There’s also an IIFE build available that you can drop straight into a page via a `<script src="https://unpkg.com/css-typed-om"></script>` tag.

~

#### 2. As an Explicit ES Module

If you prefer importing the classes directly (for example in unit tests or modular libraries):

``` js
import { CSSUnitValue, CSSKeywordValue } from 'css-typed-om';

const width = new CSSUnitValue(100, 'px');
const display = new CSSKeywordValue('block');
```

~

### [#](#wpt-tested) Tested against Web Platform Tests (WPT)

When building a polyfill for a spec as sprawling as CSS Typed OM, custom unit tests alone aren’t enough. There are hundreds of edge cases around `calc()` simplification, serialization order, out-of-range value clamping, shorthand properties, and WebIDL interface rules.

To make sure the polyfill behaves just like a native implementation, I wired the repository directly up to the official [Web Platform Tests (WPT)](https://github.com/web-platform-tests/wpt) suite. Running `npm run test:wpt` spins up headless Firefox (with its experimental native `layout.css.typed-om.enabled` flag explicitly disabled), injects the IIFE build of the polyfill, and runs all 400+ WPT test files in `/css/css-typed-om/`.

At the time of writing, the polyfill passes **12,479 out of 13,134** WPT subtests (~95%)! 🙂

**🤔 Why not 100%?** To keep the bundle size small instead of shipping a 400KB+ database of every CSS property grammar in existence, the polyfill delegates per-property syntax validation to the host browser’s native CSS parser under the hood. That means if WPT tests a bleeding-edge CSS property or keyword that the host browser itself hasn’t implemented in CSS yet, the browser’s CSS parser rejects it — which is actually what you want in practice, since `element.style` and `getComputedStyle()` stay 100% in sync with what the browser supports. An example here is the recent rename of the `anchors-valid` keyword to `anchor-valid`, which Firefox 157 which I use for testing does not support.

Additionally, some tests — like those testing `currentcolor` (or `auto` / percentages on layout properties like `width`) via `computedStyleMap()` — can never pass in a userland polyfill. That’s because `computedStyleMap()` has to read from `window.getComputedStyle()`, which returns the resolved value (such as `rgb(0, 0, 0)`) instead of the keyword `currentcolor` — leaving the polyfill with no way to know that `currentcolor` was originally specified.

~

### [#](#acknowledgements) Acknowledgements & Links

First of all, a shout-out to [Johannes Odland](https://github.com/johannesodland), who had PR’d some significant CSS Typed OM parts into the [`scroll-timeline` polyfill](https://github.com/flackr/scroll-timeline) back in the day. It’s his work that first sparked the idea of writing a full, standalone CSS Typed OM polyfill.

A special shout-out and thank you goes to [Jonathan Neal](https://github.com/jonathantneal), who kindly handed over the `css-typed-om` package name on npm *(which previously housed the now-archived [`csstools/css-typed-om`](https://github.com/csstools/css-typed-om) project)*. Thanks, Jonathan! 🙌

~

### [#](#spread-the-word) Spread the word

Feel free to reshare one of the following posts on social media to help spread the word:

~

**🔥 Like what you see? Want to stay in the loop? Here's how:**

I can also be found on [𝕏 Twitter](https://x.com/bramus) and [🐘 Mastodon](https://front-end.social/@bramus) but only post there sporadically.
