7

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?

Chris Cook
  • 474
  • 2
  • 18
  • Not sure how to explain this properly, but the variables themselves are also part of the cascade. That's why you define them in the root element - to the CSS parser, they then become properties of the node. (Although properties may not be the right word. "pseudo-properties" maybe?) Then the children of simply inherit them. – Mr Lister Jan 23 '18 at 07:58
  • @Mr Lister: See my answer. – BoltClock Jan 24 '18 at 06:17

2 Answers2

5

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.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
4

It will update if you move the calc to where you call the vars, instead of where you declare them. Demo:

:root {
  --font-size-mult: 1;
  --font-size: 16px;
}

* {
  font-size: calc(var(--font-size-mult) * var(--font-size));
}

.large {
  --font-size-mult: 2;
}
Kriszta
  • 670
  • 1
  • 7
  • 22