Search code examples
cssinheritancecss-variables

CSS Variables inheritance and fallback


My question is why CSS Variables needed to add the concept of fallback and doesn't just rely on inheritance like the other CSS properties.

For example:

:root {
 --color1: red;
}

body {
 color: var(--color1);
}

body p {
 color: var(--color2);
 --color2: blue;
}

body span {
  --color3: green;
  color: var(--color3);
  color: yellow;
  color: var(--color4);
}
<body>
  Text 1
  <p>
    Text 2
  </p>
  <span>
    Text 3
  </span>
</body>

Text 3 ends up in being red instead of green or yellow. Although there are valid properties on the level, it takes the parent color value, instead of verifying if there are other valid values on the same level. This happens when the variable name is invalid.

Apparently there is a special fallback for CSS variables so you need to use something like:

color: var(--color4, yellow);

But this means again duplication of the color, since

color: var(--color4, --color3);

does not work. Neither is:

color: var(--color3, yellow, blue);

or any other multiple values.

Also no support for keywords like inherit, currentColor, initial, etc. So I'm not sure how I could rely on the inheritance since apparently I need to be very explicit with the given values. Tested on Firefox 57.0.1 and Chrome 63.

I know the CSS variables are still as Working Draft so maybe that's why the current behavior.


Solution

  • As JoseAPL has stated, the reason var() expressions accept a fallback argument instead of defaulting to inheritance is simply because while custom properties do inherit, not all of the standard properties you will use them with do.

    On the other hand, if you're asking why a var() expression doesn't default to the next best-cascaded value, that's because by the time the var() expression is evaluated, there are no other values to fall back to, because every other declaration in the cascade has been erased. See section 3.1, which defines the term invalid at computed-value time:

    Note: The invalid at computed-value time concept exists because variables can’t "fail early" like other syntax errors can, so by the time the user agent realizes a property value is invalid, it’s already thrown away the other cascaded values.

    Having said that, you can provide a custom property as a fallback value (as long as it's not the same one, for the same reason explained above) — that custom property just needs to appear in its own var() expression, since the fallback value is, well, a declaration value, not a property name.

    So the result is a var() nested inside another var():

    body span {
      --color3: green;
      color: var(--color3);
      color: yellow;
      color: var(--color4, var(--color3));
    }
    

    :root {
     --color1: red;
    }
    
    body {
     color: var(--color1);
    }
    
    body p {
     color: var(--color2);
     --color2: blue;
    }
    
    body span {
      --color3: green;
      color: var(--color3);
      color: yellow;
      color: var(--color4, var(--color3));
    }
    <body>
      Text 1
      <p>
        Text 2
      </p>
      <span>
        Text 3
      </span>
    </body>