CSSCSS Typed Object Model

CSS Typed Object Model

For as long as CSS has existed, JavaScript has read and written it as plain strings: el.style.width = "20px", then parsed whatever came back from getComputedStyle by hand. The CSS Typed Object Model (Typed OM) replaces those strings with real typed JavaScript objects — numbers with attached units, not text you have to re-parse every time you touch it.

The problem with string-based styles

HTML
el.style.width = '100px';
const width = getComputedStyle(el).width; // "100px" — a string
const num = parseFloat(width) + 10;        // manual parsing to do math
el.style.width = num + 'px';                // manual re-stringifying

Every read is a string that must be parsed (with a manual parseFloat), and every write must be a correctly formatted string, including the unit — an easy source of subtle bugs (forgetting the px, mixing up units) and unnecessary parsing overhead when this happens inside a hot animation loop.

attributeStyleMap — typed, structured access

HTML
el.attributeStyleMap.set('width', CSS.px(120));
el.attributeStyleMap.set('opacity', 0.5);

const width = el.attributeStyleMap.get('width');
console.log(width.value, width.unit); // 120  "px" — already parsed for you
  • el.attributeStyleMap is the typed equivalent of el.style — same idea (the element's own inline style), but every value is a structured object instead of a raw string.

  • CSS.px(120), CSS.percent(50), CSS.deg(45) are factory functions that build a typed value with its unit already attached, instead of you concatenating a string.

  • Reading a value back gives you a CSSUnitValue object — .value and .unit are already split apart, no parseFloat/regex needed.

CSSUnitValue — numbers with units, and arithmetic

HTML
const a = CSS.px(10);
const b = CSS.px(5);
const sum = a.add(b);          // CSSUnitValue { value: 15, unit: "px" }
const scaled = a.mul(2);       // CSSUnitValue { value: 20, unit: "px" }

// Mixed units build a CSSMathSum instead of failing silently
const mixed = CSS.px(10).add(CSS.percent(5)); // calc(10px + 5%), kept symbolic
Note
Adding incompatible units (like px and %) does not throw or silently coerce — it produces a CSSMathSum, the typed equivalent of a CSS calc() expression, preserving both operands until the browser actually needs to resolve a final pixel value.
computedStyleMap() — typed reads of computed styles

HTML
const styleMap = el.computedStyleMap();
const fontSize = styleMap.get('font-size'); // CSSUnitValue, e.g. { value: 16, unit: "px" }
const display = styleMap.get('display');    // CSSKeywordValue { value: "flex" }

computedStyleMap() is the typed replacement for getComputedStyle() — instead of a CSSStyleDeclaration full of strings, every property resolves to the appropriate typed value class: CSSUnitValue for dimensions, CSSKeywordValue for keywords like flex or none, CSSColorValue for colors, and so on.

Numeric values without string parsing

The headline benefit is doing repeated layout math — dragging an element, driving a custom scrubber, running a physics-based animation — without a parse/stringify round trip on every single frame.

HTML
function onDrag(el, deltaX) {
  const current = el.attributeStyleMap.get('translate') ?? CSS.px(0);
  const next = current.add(CSS.px(deltaX));
  el.attributeStyleMap.set('translate', next);
  // No string concatenation, no parseFloat, on every mousemove frame
}
Use with Houdini

Typed OM is one pillar of the broader CSS Houdini project (see the CSS Houdini Overview page) — the same typed values are what get passed into a registered @property custom property, and into a Paint Worklet's style map, rather than raw strings. If you register a custom property with @property and a numeric syntax (<number>, <length>), reading it back through Typed OM gives you a proper typed value instead of text.

Performance benefit, honestly assessed

Scenario

String-based style API

Typed OM

One-off style change

Negligible difference

Negligible difference

High-frequency updates (drag, scroll-linked animation)

Parse/stringify cost repeats every frame

Values stay structured; avoids redundant parsing

Reading many computed values at once

Each is a separate string to parse

Each is already the correct typed object

Tip
The performance win is real but modest for most apps — reach for Typed OM when you are already deep in a custom animation/drag loop touching styles every frame, not as a blanket replacement for el.style everywhere.
Browser support

Typed OM shipped in Chromium-based browsers first and has broader but not universal support — check current status before depending on it for anything outside a progressive enhancement. el.style and getComputedStyle() remain fully supported everywhere and are the correct fallback.