CSS Variable with Fallback Value of Itself

CSS variable with fallback value of itself

Generally CSS can't do if/then/else, but when using i.e. var one can, kind of.

Using var() you can define a fallback value when the given
variable is not yet defined

The second (optional) argument, the declaration-value, has some limits though.

The production matches any sequence of one or more
tokens. So, as long as the sequence does not contain
, , unmatched <)-token>, <]-token>,
or <}-token>, or top-level tokens or
tokens with a value of "!" ,it represents the entirety of what a valid
declaration can have as its value.

Src:

  • MDN CSS Var
  • MDN Using CSS variables

This won't work

:root{   --myOld: lime;  --myVar: var(--myNew, --myOld) }
div { color: var(--myVar)}
<div>Hey there</div>

CSS variables defaults: set if not already set

This first attempt makes the text black. Why?

Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.

The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by @kornieff)


From the specification:

Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.

For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).

Inherit a CSS variable, or fallback to default

You can't override a variable with itself, so what I can suggest is to define a second variable that will contain your fallback value for each context:

:root {
--fallback-bg: green;
}
.some-component {
display: contents;
--fallback-bg: red;
}
.some-component__container {
background-color: var(--background-color, var(--fallback-bg));
height: 100px;
width: 100px;
}

You can also define a fallback for your fallback if you'd like:
background-color: var(--background-color, var(--fallback-bg, green));

Unable to overwrite CSS variable with its own value

As commented above, CSS is not C++ and you are having a cyclic dependency wanting to express a property with the same property which will not work.

Refering to the specification:

Custom properties are left almost entirely unevaluated, except that
they allow and evaluate the var() function in their value. This can
create cyclic dependencies where a custom property uses a var()
referring to itself, or two or more custom properties each attempt to
refer to each other.

A common fix to that situation is to add more variables to avoid any cyclic dependency.

:root {
--bs:50px;
--bl:16px;
--box-size:var(--bs);
--box--larger:var(--bl);
}

.box {
width: var(--box-size);
height: var(--box-size);
border: 1px solid red;
}

.box--larger {
--box-size: 66px;
}

.box--larger1 {
--box-size: calc(var(--bs) + var(--bl));
}

.box--larger2 {
--box-size: calc(50px + var(--bl));
}
<div class="box">1</div>
<div class="box box--larger">2</div>
<div class="box box--larger1">3</div>
<div class="box box--larger2">4</div>

CSS Variables inheritance and fallback

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>

CSS — ::selection with var() fallback

The only way to obtain what you want is to make sure the value is invalid so the browser will ignore it and fall back to the default one:

::selection {
background-color: something_invalid;
}
<p>a b c</p>
<div>
<p>x y z</p>
</div>

Does CSS var() fallback work in unsupported browsers?

Will it work in older browsers or no?

No it won't. If a browser doesn't recognize var() then there is no way to consider the fallback value you defined there. The whole value will be invalid.

You should stick to your first syntax if you want to consider old browsers.

How to set CSS variable to the value unset, --unset-it: unset?

Like I did in this previous answer with inherit You need to make unset the fallback value and consider initial to activate it:

* {  box-sizing: border-box;}
#ex { background: yellow; padding: 0.5rem; margin: 1rem 0;}

#div1 { --unset-it: 2px dotted red; outline: var(--unset-it, unset);}
<div id="ex">  <p id="div1">    outline: 2px dotted red;  </p>  <p id="div1" style="--unset-it:initial">    outline: unset;  </p></div>

how to reset a CSS variable (aka custom properties) and use the fallback one?

You can unset the value using initial to use the fallback one:

:root {  --border-width-top: 2px;  --border-width-right: 2px;  --border-width-bottom: 2px;  --border-width-left: 2px;  --border-width: 0;}div {  margin:5px;  border-color: red;  border-style: solid;  border-width: var(--border-width, var(--border-width-top) var(--border-width-right) var(--border-width-bottom) var(--border-width-left));}

div.box { --border-width:initial; --border-width-top: 10px;}
<div>some content</div><div class="box">some content</div>


Related Topics



Leave a reply



Submit