Register custom properties in CSS, get and update them with JavaScript

In this article we’ll look at two different ways to get the value of a CSS custom property and set or override the value of a custom property using JavaScript. The first is supported in all browsers. The second approach uses the Typed OM, which isn’t supported in Firefox just yet.

We’ll then learn how to register a custom property as being of a particular type.

Let’s say we have a --size variable defined on the :root/html element:

:root {
    --size: 24px;

Here’s how you can obtain the value of that custom property in JavaScript:


Here’s how you set a CSS custom property inline on the :root/html element with JavaScript:'--accent-color', 'rgb(100,110,10)');

Now let’s take a look at doing the same thing with the Typed OM API.

Setting a custom property using the Typed OM API

We can use attributeStyleMap.set() to set a custom property as an inline style:

document.documentElement.attributeStyleMap.set('--size', '20px');

Somewhat surprisingly, the following does not work and will cause an error, regardless of whether you have registered the property or not:

document.documentElement.attributeStyleMap.set('--size', CSS.px(20));

Getting custom properties using the Typed OM API

To get the value of a custom property we use computedStyleMap().get():

const size = document.documentElement.computedStyleMap().get("--size");

If you console.log size now, it’ll show CSSUnparsedValue {0: '24px', length: 1}. You’ll notice that '24px' is a string. By using CSSNumericValue.parse() we can get the value as a number and the unit as a string.

const parsedSize = CSSNumericValue.parse(document.documentElement.computedStyleMap().get("--size"))

Now a console.log will show CSSUnitValue {value: 24, unit: 'px'}

Needing to use CSSNumericValue.parse() doesn’t feel great. We can define the type of a custom property by registering it in CSS or JavaScript. Let’s do that instead.

Register a custom property in CSS with @property

When we define a custom property, the browser has no way of knowing what kind of value it contains. The CSS Properties and Values API provides a way to register a custom property as being of a particular type.

You define @property outside of any selector - meaning rather than html { @property ...} or :root {@property ...} you just write:

@property --size {
    syntax: "<length>";
    inherits: true;
    initial-value: 24px;

syntax is where we specify a type. It can be one of "<length>", "<number>", "<percentage>", "<length-percentage>" (any valid length or percentage value, any valid calc() expression combining length and percentage components), "<color>", "<image>", "<url>", "<integer>", "<angle>", "<time>", "<resolution>", "<transform-function>", "<transform-list>" or "<custom-ident>".

Registering a custom property has the benefit of making the custom property animatable in CSS. It also means that JavaScript will know what type of value the custom property contains. To go back to our previous code, we can easily get the value and the unit of the CSS variable separately with JavaScript without needing CSSNumericValue.parse():

const sizeAsNumber = document.documentElement.computedStyleMap().get("--size").value 
const sizeUnit = document.documentElement.computedStyleMap().get("--size").unit

Register a custom property in JavaScript

While the CSS approach is better, we could alternatively register a custom property with JavaScript. The equivalent to the above @property code would be:

    name: "--size",
    syntax: "<length>",
    inherits: true,
    initialValue: "24px"

Browser support

Firefox does not yet support @property but has expressed a positive position on the proposal. @property is part of Interop 2023 so will probably be implemented in Firefox at some point this year. Firefox does not support the Typed OM API but has a positive position on the proposal (Typed OM is not part of Interop 2023 and there is no ETA). There are no up-to-date production-ready polyfills for Typed OM. Both @property and Typed OM are part of the Houdini set of API’s. tracks the progress of Houdini.