Reading and writing CSS values with types, and faster, in JavaScript
The classic JavaScript style API only knows strings: style.width returns "320px" no matter whether it is a length, an angle, or a plain number, and any calculation with it starts with parsing and ends with rebuilding a string. The CSS Typed OM replaces that with real, typed objects that can be added, multiplied, and converted between units directly, without the detour through text.
Table of Contents
- 1. The problem with string-based CSS: style.cssText and getPropertyValue
- 2. attributeStyleMap: the typed replacement for element.style
- 3. Typed values in detail: CSSUnitValue, CSSKeywordValue, CSSTransformValue
- 4. computedStyleMap() vs. getComputedStyle(): the practical comparison
- 5. Performance: avoiding parsing overhead in animation loops
- 6. Reading and writing custom properties through the Typed OM
- 7. Arithmetic with CSSNumericValue: add, mul, and to()
- 8. Browser support and a robust fallback pattern
- 9. Practical example: a drag handler using CSS Typed OM
- 10. Summary
- 11. FAQ
1. The problem with string-based CSS: style.cssText and getPropertyValue
Anyone working with element.style.width = "320px" today is really manipulating a plain text string that the browser has to re-parse on every access to turn it into an internal number with a unit. For a single assignment that detour is unnoticeable, but once a script reads, computes, and writes values frequently, for example in an animation or a drag handler, the repeated parsing and serializing adds up to real, measurable overhead.
The CSS Typed OM (Object Model) solves this by representing CSS values as structured JavaScript objects, for example CSSUnitValue for a number with a unit or CSSKeywordValue for keywords like auto. These objects carry their type and unit directly, so calculations like "add 10px" need no string parsing, and the browser can reuse the same internal representation it needs for rendering anyway.
2. attributeStyleMap: the typed replacement for element.style
The central entry point of the Typed OM is element.attributeStyleMap, a map-like interface with get(property) and set(property, value). Instead of assigning a string, you pass a CSSStyleValue object created through helper functions like CSS.px(320), which automatically carries the right unit, so a unit typo never surfaces as a broken layout only at runtime.
The return value of get() is typed as well: instead of a string like "320px", you get a CSSUnitValue object with the properties value (the number 320) and unit (the string "px"), which can be processed directly without writing a regular expression to split number and unit apart.
const box = document.querySelector('.box');
// Classic string-based style API
box.style.width = '320px';
const widthString = box.style.width; // "320px" -- needs parsing to use
// CSS Typed OM: typed values in and out
box.attributeStyleMap.set('width', CSS.px(320));
const widthValue = box.attributeStyleMap.get('width'); // CSSUnitValue
console.log(widthValue.value, widthValue.unit); // 320 "px"
3. Typed values in detail: CSSUnitValue, CSSKeywordValue, CSSTransformValue
The Typed OM distinguishes several concrete value types, all inheriting from the shared base class CSSStyleValue. CSSUnitValue covers numbers with a unit, from px through % to deg, while CSSKeywordValue represents plain keywords such as auto or inherit and can be cleanly told apart from a numeric length, something a single string never allowed in a type-safe way.
For composite properties like transform, there is CSSTransformValue, which holds a list of individual transform components (translation, rotation, scaling) as their own objects instead of hand-assembling a single function-string chain like "translateX(10px) rotate(5deg)". That makes it possible to change just the rotation of an existing transform chain without re-parsing and reassembling the whole string.
4. computedStyleMap() vs. getComputedStyle(): the practical comparison
getComputedStyle(element) returns a serialized string for every property, even for plain numeric values, and can force a reflow on any access to a layout-dependent property whenever the layout is currently marked dirty. element.computedStyleMap() instead returns those same computed values as typed CSSStyleValue objects, removing downstream parsing entirely once the value is meant to be used in a further calculation.
In practice the difference is clearest with composite values like font or background: getComputedStyle returns a single, often hard-to-split string for those, while computedStyleMap() exposes the individual values through get('font-size') and get('background-color') directly and already typed, with no need to write a custom mini-parser for the composite string.
const box = document.querySelector('.box');
// Classic: always a string, even for pure numbers
const opacityString = getComputedStyle(box).opacity; // "0.5"
const opacityNumber = parseFloat(opacityString);
// Typed OM: already a typed CSSUnitValue with .value as a real number
const opacityValue = box.computedStyleMap().get('opacity');
console.log(opacityValue.value); // 0.5, no parseFloat needed
5. Performance: avoiding parsing overhead in animation loops
In a requestAnimationFrame loop that adjusts an element's position or size every frame, the overhead of string parsing adds up measurably at sixty frames per second, especially with several elements animating at once. Every assignment of element.style.left = value + "px" forces a string concatenation and subsequent parsing by the CSS engine, while attributeStyleMap.set('left', CSS.px(value)) skips that detour entirely.
The measured difference per individual call is tiny, but in data-heavy applications such as canvas overlays, drag-and-drop libraries, or charts with hundreds of animated DOM nodes, it adds up to a noticeable difference in profiling. For most everyday UI interactions, the Typed OM mainly brings type safety and more readable code; the real performance win shows up under high update frequency and many elements.
6. Reading and writing custom properties through the Typed OM
CSS custom properties can also be set and read through attributeStyleMap, but get('--my-color') returns a CSSUnparsedValue by default, because without @property registration the browser has no concrete type to assign. Only registering with @property and a matching syntax declaration makes the same custom property come back as a typed value like CSSUnitValue, instead of an unstructured text token.
This combination of @property and Typed OM is especially valuable for design-system tokens, when a script needs to read a spacing value from a custom property, modify it in JavaScript, and write it back type-safely, without carrying along its own parsing logic for the relevant unit every single time.
7. Arithmetic with CSSNumericValue: add, mul, and to()
Numeric Typed OM values inherit from CSSNumericValue and come with built-in methods like add(), sub(), mul(), and to(unit), which let you compute directly with values without first manually converting them into a number. A script can write CSS.px(100).add(CSS.px(20)) and get back a new, correctly typed CSSUnitValue, instead of gluing strings together and hoping a unit typo does not slip through.
The to() method also allows unit conversion, for example from rem to px, directly in the object model, which is especially useful when a script needs to compare or add two values with different units. Without the Typed OM, the same conversion would have to happen manually through the root element's computed font-size, an error-prone intermediate step the Typed OM encapsulates entirely.
const base = CSS.px(100);
const extra = CSS.px(20);
const total = base.add(extra); // CSSUnitValue: 120px
console.log(total.value, total.unit); // 120 "px"
// Convert between compatible units directly in the object model
const remValue = CSS.rem(2);
const pxEquivalent = remValue.to('px'); // requires a resolved context in practice
8. Browser support and a robust fallback pattern
Chrome and Edge fully support the CSS Typed OM, Firefox implements only parts of it, and Safari so far offers no support for attributeStyleMap or computedStyleMap(). For production code that means the Typed OM should only be used where a fallback to the classic string API exists, for example in utility functions that first check whether CSS.number is available.
The most reliable feature detection is if ('attributeStyleMap' in Element.prototype), because it checks the actually needed interface directly, instead of relying on a browser version check that quickly goes stale with future browser updates. Inside such a utility function, the same logical operation can then be expressed through either the Typed OM or the classic style API.
function setWidthPx(element, px) {
if ('attributeStyleMap' in Element.prototype) {
element.attributeStyleMap.set('width', CSS.px(px));
} else {
element.style.width = `${px}px`;
}
}
9. Practical example: a drag handler using CSS Typed OM
A drag handler that updates an element's position on every pointermove event benefits especially from the Typed OM, because it triggers a very large number of style updates in a short time. Instead of assembling a new string like `${x}px` on every frame, the handler reads the current position in typed form, adds the mouse movement directly as a CSSUnitValue, and writes the result back without any string-concatenation detour.
This pattern not only reduces parsing overhead, it also makes the code more robust against unit mistakes, because CSS.px() always produces a valid, typed length, and accidentally mixing pixels with percent surfaces at runtime as a clear error instead of silently producing a broken layout.
| Task | Classic style API | CSS Typed OM | Advantage of the Typed OM |
|---|---|---|---|
| Reading a value | getComputedStyle(el).width | el.computedStyleMap().get('width') | Typed object instead of a string |
| Writing a value | el.style.width = '320px' | el.attributeStyleMap.set('width', CSS.px(320)) | No unit typo possible |
| Computing | parseFloat(...) + 20 + 'px' | value.add(CSS.px(20)) | No manual string assembly |
| Reading a custom property | getPropertyValue('--x') | attributeStyleMap.get('--x') (typed with @property) | Type safety for design tokens |
Mironsoft
Modern CSS, layout architecture and rendering performance
CSS that stays maintainable instead of breaking with every change?
We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.
CSS Audit
Systematically uncovering specificity issues, cascade conflicts and unused selectors.
Architecture Refactoring
Introducing cascade layers, custom properties and design tokens cleanly.
Performance Tuning
Fixing layout thrashing, expensive selectors and rendering bottlenecks.
10. Summary
CSS Typed OM: The Essentials at a Glance
Core idea
The Typed OM represents CSS values as structured objects (CSSUnitValue, CSSKeywordValue) instead of plain strings.
Entry points
attributeStyleMap replaces element.style, computedStyleMap() replaces getComputedStyle() with typed return values.
Arithmetic
CSSNumericValue comes with add(), sub(), mul(), and to() to compute with values directly, without string parsing.
Support reality
Full in Chrome/Edge, partial in Firefox, missing in Safari. Always pair it with feature detection and a fallback.