CSS Typed OM from JavaScript
CSS values have always been strings in JavaScript. Reading element.style.width gives you "100px" — a string you then have to parse, split, and convert yourself. Arithmetic on those strings doesn’t work at all: "100px" + "50px" is "100px50px", not "150px".
CSS Typed OM fixes this by giving you native JavaScript objects that represent CSS values. Instead of strings, you work with CSSUnitValue, CSSMathValue, and CSSTransformValue objects. The API ships in all modern browsers and covers every CSS property on every element.
The Problem with String-Based CSS
When you read a width value the old way, you get a string:
const width = element.style.width;
// → "100px"
To use it numerically, you reach for parseInt, parseFloat, or a regex. To set a new value, you construct another string. To combine two values — impossible without string gymnastics.
// String-based approach: fragile
const width = parseInt(element.style.width, 10); // 100
const newWidth = width + 50;
element.style.width = newWidth + 'px'; // "150px"
This breaks the moment the unit changes or the value uses calc(). There is no type safety, no unit access, and no way to do math directly.
Reading Inline Styles with attributeStyleMap
Every element has an attributeStyleMap property that exposes its inline styles as a typed StylePropertyMap. This replaces the string-based element.style API.
Setting a Typed Value
const el = document.querySelector('#box');
el.attributeStyleMap.set('width', CSS.px(100));
el.attributeStyleMap.set('height', CSS.px(100));
el.attributeStyleMap.set('background', 'tomato');
CSS.px(), CSS.em(), CSS.deg(), and dozens more are factory methods on the global CSS object. They create CSSUnitValue instances so you never have to write new CSSUnitValue(100, 'px').
Reading a Typed Value Back
const w = el.attributeStyleMap.get('width');
// → CSSUnitValue { value: 100, unit: "px" }
console.log(w.value); // 100
console.log(w.unit); // "px"
console.log(w.toString()); // "100px"
The value is a proper JavaScript object. You access the number directly as .value and the unit as .unit.
Checking and Deleting Properties
el.attributeStyleMap.has('width'); // → true
el.attributeStyleMap.delete('width'); // removes the property
el.attributeStyleMap.has('width'); // → false
Other attributeStyleMap Operations
// Check how many properties are set
el.attributeStyleMap.size; // → 2
// Iterate over all set properties
for (const [prop, val] of el.attributeStyleMap) {
console.log(prop, val.toString());
}
Reading Computed Styles with computedStyleMap()
The computedStyleMap() method returns a read-only StylePropertyMapReadOnly containing fully computed values — after cascade, inheritance, and custom property resolution. Unlike attributeStyleMap, calling it always returns a fresh snapshot.
const map = element.computedStyleMap();
const fontSize = map.get('font-size');
// → CSSUnitValue { value: 16, unit: "px" } (resolved from em/rem if used)
This is useful when you need the final pixel value regardless of what unit the stylesheet used.
Converting Between Units
CSSUnitValue objects expose a .to(unit) method for converting between compatible units:
const map = element.computedStyleMap();
const fontSize = map.get('font-size');
// → CSSUnitValue { value: 2, unit: "em" }
const pxSize = fontSize.to('px');
// → CSSUnitValue { value: 32, unit: "px" } (assumes 16px root)
Not all conversions are valid — you cannot convert px to deg, for example. The method throws a TypeError for incompatible unit pairs.
Arithmetic with CSSMathValue
CSSMathValue represents CSS calc() expressions. Its subclasses correspond to each operation:
CSSMathSum—calc(a + b)CSSMathProduct—calc(a * b)CSSMathMin—calc(min(a, b))CSSMathMax—calc(max(a, b))CSSMathNegate—calc(-a)
Adding Two Length Values
const a = new CSSUnitValue(10, 'px');
const b = new CSSUnitValue(5, 'px');
const sum = new CSSMathSum(a, b);
console.log(sum.toString()); // "calc(10px + 5px)"
Multiplying a Value
const base = new CSSUnitValue(3, 'em');
const scaled = new CSSMathProduct(base, CSS.number(2));
console.log(scaled.toString()); // "calc(3em * 2)"
// Use it directly in the style map
element.attributeStyleMap.set('font-size', scaled);
Negating a Value
const offset = new CSSUnitValue(20, 'px');
const negated = new CSSMathNegate(offset);
element.attributeStyleMap.set('margin-left', negated);
// → margin-left: calc(-20px)
Chaining Math Operations
const result = new CSSMathSum(
new CSSUnitValue(100, 'vw'),
new CSSMathProduct(
new CSSUnitValue(-2, 'rem'),
CSS.number(10)
)
);
// → calc(100vw + -2rem * 10)
The nesting maps directly to how calc() works in CSS.
Composing Transforms with CSSTransformValue
CSS transform properties accept multiple functions in sequence. CSSTransformValue holds an array of CSSTransformComponent objects — one per transform function.
Creating a Transform Value
const transform = new CSSTransformValue([
new CSSTranslate(CSS.px(50), CSS.px(100)),
new CSSRotate(CSS.deg(45)),
new CSSScale(CSS.number(1.5), CSS.number(1.5))
]);
element.attributeStyleMap.set('transform', transform);
Available Transform Components
| Class | CSS equivalent |
|---|---|
CSSTranslate(x, y, z) | translate(x, y, z) |
CSSRotate(angle) | rotate(angle) |
CSSScale(x, y, z) | scale(x, y, z) |
CSSSkew(ax, ay) | skew(ax, ay) |
CSSPerspective(length) | perspective(length) |
Reading a Transform Back
const current = element.attributeStyleMap.get('transform');
// → CSSTransformValue [ CSSTranslate(...), CSSRotate(...), CSSScale(...) ]
Inverting a Transform
const original = element.attributeStyleMap.get('transform');
const inverted = original.toMatrix().inverse;
CSSTransformValue composes cleanly — you can extract the matrix representation and derive from it.
Typed vs String: A Side-by-Side Comparison
// === OLD: string-based ===
// Read
const width = parseInt(getComputedStyle(el).width, 10); // manual parse
// Write
el.style.width = (width + 50) + 'px'; // string concat everywhere
// Math
parseInt(el.style.width, 10) + parseInt(otherEl.style.width, 10); // error-prone
// Transform
el.style.transform = `rotate(${angle}deg) scale(${scale})`; // template literal every time
// === NEW: typed om ===
// Read
const width = el.computedStyleMap().get('width'); // CSSUnitValue, no parsing
// Write
el.attributeStyleMap.set('width', CSS.px(width.value + 50)); // typed arithmetic
// Math
new CSSMathSum(width, otherWidth).toString(); // "calc(Xpx + Ypx)"
// Transform
el.attributeStyleMap.set('transform',
new CSSTransformValue([CSSRotate(CSS.deg(angle)), CSSScale(CSS.number(scale))])
);
Use Case: Responsive Layout Calculations
When building layout code that reads dimensions from the DOM and derives new values, Typed OM removes the parsing overhead entirely:
function getElementSize(el) {
const map = el.computedStyleMap();
return {
width: map.get('width').to('px').value,
height: map.get('height').to('px').value
};
}
function setScaledSize(el, scale) {
const { width, height } = getElementSize(el);
el.attributeStyleMap.set('width', CSS.px(width * scale));
el.attributeStyleMap.set('height', CSS.px(height * scale));
}
Every value you read is already a number. Every value you write is already typed. No parseInt calls, no template literals, no regex.
Use Case: Animations with the Web Animations API
The Web Animations API pairs naturally with Typed OM. Keyframes use typed values directly — no string interpolation:
const el = document.querySelector('.slide-in');
const startTransform = new CSSTransformValue([
new CSSTranslate(CSS.px(-200), CSS.px(0))
]);
const endTransform = new CSSTransformValue([
new CSSTranslate(CSS.px(0), CSS.px(0))
]);
const animation = el.animate([
{ transform: startTransform, opacity: CSS.number(0) },
{ transform: endTransform, opacity: CSS.number(1) }
], {
duration: 600,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
fill: 'forwards'
});
Because animate() accepts CSSStyleValue objects in keyframes, you compose transforms and transition opacity values without ever touching a string.
Working with Custom Properties (CSS Variables)
Custom properties resolve through computedStyleMap() the same as any other property:
const map = element.computedStyleMap();
const hue = map.get('--brand-hue');
if (hue instanceof CSSUnitValue) {
console.log(`Brand hue: ${hue.value}deg`);
}
// Set a custom property with a typed value
element.attributeStyleMap.set('--rotation', CSS.deg(90));
console.log(element.attributeStyleMap.get('--rotation').toString()); // "90deg"
Custom properties store their resolved type, so checking instanceof CSSUnitValue before accessing .value is good practice.
Summary
CSS Typed OM replaces string manipulation with typed JavaScript objects:
attributeStyleMap— read and write inline styles on an element as typed valuescomputedStyleMap()— read the fully computed, resolved value of any CSS propertyCSSUnitValue— a number with an associated unit (100px,1.5em,45deg)CSSMathValue— arithmetic (calc()) as nested typed objectsCSSTransformValue— composed transform functions as typed objects
The global CSS object provides factory methods (CSS.px(), CSS.deg(), CSS.number()) for creating values without explicit constructors. Values convert back to strings with .toString().
Browser support covers Chrome 66+, Firefox 85+, Safari 16.4+, and Edge 79+. Older browsers degrade values to strings gracefully — you can feature-detect with a simple check:
if (element.attributeStyleMap) {
// Use Typed OM
} else {
// Fall back to string-based style API
}