Skip to content

Regression in 20.12.1: only the first var() in a value is resolved, shorthand with multiple var() is dropped from getComputedStyle() #2344

Description

@VladimirFromMoscow

Describe the bug
Since v20.12.1, when a declaration value contains two or more var() references, only the first one is resolved. The remaining var() stays unresolved in the computed value. For shorthand properties (padding, margin, …) the partially resolved value then fails validation and the whole declaration is dropped, so getComputedStyle() returns "" for the shorthand and for all its longhands.

v20.12.0 resolves all of them correctly. This is a regression introduced by #2341 (the fix for #2256).

This breaks any MUI (Material UI) test suite that uses cssVariables: true: MUI's theme.spacing(a, b) emits calc(a * var(--mui-spacing)) calc(b * var(--mui-spacing)).

To Reproduce

import { Window } from "happy-dom";

const window = new Window();
const document = window.document;

document.body.innerHTML = `
  <style>:root { --x: 16px; }</style>
  <style>
    .a { padding: calc(0.375 * var(--x)) calc(0.75 * var(--x)); }
    .b { padding: var(--x) var(--x); }
    .c { margin: calc(0.375 * var(--x)) calc(0.75 * var(--x)); }
    .d { padding: calc(0.375 * var(--x)); }
  </style>
  <div class="a"></div><div class="b"></div><div class="c"></div><div class="d"></div>
`;

for (const cls of ["a", "b", "c", "d"]) {
  const cs = window.getComputedStyle(document.querySelector("." + cls));
  console.log(cls, JSON.stringify({ padding: cs.padding, paddingTop: cs.paddingTop, margin: cs.margin }));
}

Output with v20.12.1 / v20.12.2:

a {"padding":"","paddingTop":"","margin":""}
b {"padding":"16px var(--x)","paddingTop":"16px","margin":""}
c {"padding":"","paddingTop":"","margin":""}
d {"padding":"calc(0.375 * 16px)","paddingTop":"calc(0.375 * 16px)","margin":""}

Output with v20.12.0 (and v20.11.x):

a {"padding":"calc(0.375 * 16px) calc(0.75 * 16px)","paddingTop":"calc(0.375 * 16px)","margin":""}
b {"padding":"16px","paddingTop":"16px","margin":""}
c {"padding":"","paddingTop":"","margin":"calc(0.375 * 16px) calc(0.75 * 16px)"}
d {"padding":"calc(0.375 * 16px)","paddingTop":"calc(0.375 * 16px)","margin":""}

Case b shows the root cause directly: the second var(--x) is left untouched. The same happens with element.style.cssText = "padding: calc(0.375 * var(--x)) calc(0.75 * var(--x))" (the inline declaration is dropped as well).

Expected behavior
Every var() in the value is resolved, as in v20.12.0: .a → calc(0.375 * 16px) calc(0.75 * 16px), .b → 16px, .c margin → calc(0.375 * 16px) calc(0.75 * 16px).

Device and details:

  • OS: macOS 26 (Darwin 25.5.0)
  • Node version: 24.16.0
  • Package version: 20.12.1, 20.12.2 (20.12.0 works)

Additional context
The cause is in CSSVariableFormatter.resolveVariables(): after the first var() is substituted, the tail of the string is appended verbatim instead of being resolved recursively. Applying resolveVariables to the tail fixes all cases above; with that one-line change our full test suite (~2000 tests, MUI v9 with CSS variables) passes on v20.12.2:

- return `${value.substring(0, variable.index)}${this.resolveVariables(variableValue || fallbackValue, cssVariables)}${value.substring(match.index! + match[0].length)}`;
+ return `${value.substring(0, variable.index)}${this.resolveVariables(variableValue || fallbackValue, cssVariables)}${this.resolveVariables(value.substring(match.index! + match[0].length), cssVariables)}`;

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions