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

Code example showing the CSS Typed OM, with the css-typed-om polyfill loaded on the first line.

Being a big fan of the CSS Typed OM, 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.

~

# 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.

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) 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).

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). 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

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 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 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. 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 & 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:

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):

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

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

~

# 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) 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 & Links

First of all, a shout-out to Johannes Odland, who had PR’d some significant CSS Typed OM parts into the scroll-timeline polyfill 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, who kindly handed over the css-typed-om package name on npm (which previously housed the now-archived csstools/css-typed-om project). Thanks, Jonathan! 🙌

~

# 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 and 🐘 Mastodon but only post there sporadically.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.