9

I am trying to have CSS variables for box model properties. I want to support both setting a value for all sides as well as individual sides. I want to have default values, but be override-able either way. I tries using fallback values, but with little success.

Something like:

:root {
  --border-width-top: 0;
  --border-width-right: 0;
  --border-width-bottom: 0;
  --border-width-left: 0;
  --border-width: 0;
}
div {
  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 {
  --border-width-top: 10px;
}

This will not work as if border-width has a default value then it will always take precedence over the fallback values. Not sure there is a way to do this currently, but I feel so close to finding a solution.

Here is a stackblitz I am playing with: stackblitz

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
masimplo
  • 3,674
  • 2
  • 30
  • 47
  • 1
    Kukkuz' answer works for your particular example, but I'm afraid you're really asking if there's a way to "un-define" variables defined higher up in the DOM tree. Or am I overthinking things? – Mr Lister Apr 10 '19 at 14:51
  • 1
    @MrLister you are right, see Temani's solution – kukkuz Apr 10 '19 at 15:05

4 Answers4

16

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>

from the the specification:

The initial value of a custom property is an empty value; that is, nothing at all. This initial value has a special interaction with the var() notation, which is explained in the section defining var().

and

To substitute a var() in a property’s value:

  1. If the custom property named by the first argument to the var() function is animation-tainted, and the var() function is being used in the animation property or one of its longhands, treat the custom property as having its initial value for the rest of this algorithm.
  2. If the value of the custom property named by the first argument to the var() function is anything but the initial value, replace the var() function by the value of the corresponding custom property. Otherwise,
  3. if the var() function has a fallback value as its second argument, replace the var() function by the fallback value. If there are any var() references in the fallback, substitute them as well.
  4. Otherwise, the property containing the var() function is invalid at computed-value time
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Nice. I did not know that about initial. Your example complicates the usage a bit since I have to make sure I define --border-width as initial otherwise the side value is ignored. However I tried setting it as the default value inside the root and it seems to do exactly what I need. Is there something I might be missing by going this way? – masimplo Apr 10 '19 at 15:19
  • @masimplo no, it's the same. Setting initial inside root or within the element will do exactly the same since at the evaluation time it will be initial so we don't care *where* you set the initial. We simply care about the value of the VAR when doing the evaluation. – Temani Afif Apr 10 '19 at 15:24
4

As @Temani explained you can use --varName: initial to set what is effectively an empty but invalid value for a variable. Then any fallback values declared in var(....) will be used.

So if you define --text_color: initial then use the selector: color: var(--text_color, hotpink) your text will be in pink and not black (which it would be if you directly wrote color: initial).


I then wondered if perhaps I could use unset instead of initial. It just seemed more natural to undefine a variable using --text_color: unset. It's more intuitive and since you probably rarely use unset you'll remember your intent.

However it turns out unset doesn't work exactly same as initial, but may occasionally be useful if you need to inherit variables declared higher up.

--text_color: initial

  • Sets the value of the variable to an empty value (see the referenced spec in Tamani's answer)
  • Any ancestor nodes that set --text_color will be overriden by this declaration
  • I like to think of this as if you could set --text_color: null

--text_color: unset

  • Effectively deletes the variable completely from that DOM node. It doesn't set it to the magic empty value - it behaves the same way as if you just deleted it from your code altogether.
  • This allows any inherited value to be used.
  • I like to think of this as if you could set --text_color: undefined
  • So if any ancestor DOM node defines --text_color: green and your node declares --text_color: unset along with color: var(--text_color, blue) it will be green and not blue.

This was useful to me because I'm setting a lot of variables dynamically and needed to unset it as if it had never been set.

Whether or not this is the actual spec behavior I cannot promise :-)

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
3

The fallback value works only if the variable --border-width is not defined:

The custom property's fallback value, which is used in case the custom property is invalid in the used context.

MDN

See demo below:

:root {
  --border-width-top: 0;
  --border-width-right: 0;
  --border-width-bottom: 0;
  --border-width-left: 0;
  /*--border-width: 0;*/
}

div {
  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 {
  --border-width-top: 10px;
}
<div id="app">
  <h1>TypeScript Starter</h1>
</div>
kukkuz
  • 41,512
  • 6
  • 59
  • 95
  • 1
    Yes, that was my understanding as well. I am looking for a way to define the variable, but not assign a value to it. This is so that it will be present in the documentation but not affect the border unless set explicitly. Thanks for the answer. – masimplo Apr 10 '19 at 15:18
-1

You can also do this in Javascript:

element.style.setProperty('--button_icon_active_color', '')
element.style.setProperty('--button_icon_active_color', null)

These will both 'unset' the value as if it were never there.

If you run this command in the browser console you'll see it disappear from the styles.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689