As can be seen in the pen below, css variables which use calc don't seem to respect the cascade nature of the variables:
:root {
--font-size-mult: 1;
--font-size: calc(var(--font-size-mult) * 16px);
}
* {
font-size: var(--font-size);
}
.large {
--font-size-mult: 2;
}
The expected behaviour is that any part of the document which has the .large class would have a font-size twice as big as the regular 16px. What can be seen in the example is that if I redefine the font-size variable inside of .large, I get the desired behaviour, but this seems counter-intuitive since the value of --font-size is already what it needs to be to resolve to the correct value.
This seems to be the case in all browsers I have tested, so it is probably part of the spec, but it doesn't seem to fit very well with how CSS works. I would think variable values would be calculated based on the values of other variables in the current element's scope, rather than the scope of where the variable was defined.
https://codepen.io/Smilebags/pen/MrRKMY
Is this expected behaviour? Should this be the case?
Yes, this is expected behavior and in fact completely respectful of the cascading nature of custom properties. From section 2.2 of the spec:
It is important to note that custom properties resolve any var() functions in their values at computed-value time, which occurs before the value is inherited.
This means that the value of the custom property --font-size
as it appears on the root element is really calc(1 * 16px)
, not calc(var(--font-size-mult) * 16px)
, because the var(--font-size-mult)
expression is evaluated when --font-size
is computed for the root element.
This computed value, calc(1 * 16px)
, is then inherited by descendants. Any new value you set for --font-size-mult
on any descendants is ignored (unless other references to it exist).
Should this be the expected behavior? Well, I can only tell you that the spec claims this is required to prevent cyclic references between ancestors and descendants. In the same paragraph as the sentence quoted above:
In general, cyclic dependencies occur only when multiple custom properties on the same element refer to each other; custom properties defined on elements higher in the element tree can never cause a cyclic reference with properties defined on elements lower in the element tree.
Finally, while Kriszta's answer demonstrates the right way to use calc()
with custom properties taking inheritance into account, you should be using the rem unit instead of custom properties entirely, as that unit was made specifically for this use case.