search

Home  >  Q&A  >  body text

CSS scope custom properties are not recognized when used to evaluate variables in the parent scope

I'm trying to scale the size via a var custom property so that the classes can be composed without coupling. The desired effect is that the 3 lists will have 3 different scales, but as shown on CodePen, all 3 lists have the same scale. I'm looking for an explanation of scoping and CSS custom property techniques that can achieve this with composable, loosely coupled code.


:root {
  --size-1: calc(1 * var(--scale, 1) * 1rem);
  --size-2: calc(2 * var(--scale, 1) * 1rem);
  --size-3: calc(3 * var(--scale, 1) * 1rem);
}

.size-1 { font-size: var(--size-1) }
.size-2 { font-size: var(--size-2) }
.size-3 { font-size: var(--size-3) }

.scale-1x { --scale: 1 }
.scale-2x { --scale: 2 }
.scale-3x { --scale: 3 }

html {
  font: 1em sans-serif;
  background: papayawhip;
}

ol {
  float: left;
  list-style: none;
  margin: 1rem;
}
<ol class="scale-1x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-2x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-3x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>


P粉006847750P粉006847750392 days ago666

reply all(1)I'll reply

  • P粉440453689

    P粉4404536892023-11-02 11:27:45

    In your case you have evaluated the --scale custom properties at the root level to define the --size-* properties and then defined the - -scale Again within child elements. This will not trigger the evaluation again as it has already been done in the upper layer.

    The following is a simple example to illustrate this problem:

    .box {
      --color: var(--c, blue);
    }
    
    span {
      color: var(--color);
    }
    <div>
      <div class="box"><!-- --c is evaluated at this level -->
        <span style="--c:red">I will not be red because the property is already evaluated and --color is set to blue using the default value</span>
      </div>
    </div>
    
    <div style="--c:red">
      <div class="box"><!-- --c is evaluated at this level -->
        <span>I will be red because at the time of the evaluation --c is red (inherited from the upper div)</span>
      </div>
    </div>

    To solve your problem, you need to move the declaration from :root to the same level as the --scale definition:

    .scale {
      --size-1: calc(1 * var(--scale, 1) * 1rem);
      --size-2: calc(2 * var(--scale, 1) * 1rem);
      --size-3: calc(3 * var(--scale, 1) * 1rem);
    }
    
    .size-1 { font-size: var(--size-1) }
    .size-2 { font-size: var(--size-2) }
    .size-3 { font-size: var(--size-3) }
    
    .scale-1x { --scale: 1 }
    .scale-2x { --scale: 2 }
    .scale-3x { --scale: 3 }
    
    
    html {
      font: 1em sans-serif;
      background: papayawhip;
    }
    
    ol {
      float: left;
      list-style: none;
      margin: 1rem;
    }
    <ol class="scale-1x scale">
      <li class="size-1">size 1</li>
      <li class="size-2">size 2</li>
      <li class="size-3">size 3</li>
    </ol>
    <ol class="scale-2x scale">
      <li class="size-1">size 1</li>
      <li class="size-2">size 2</li>
      <li class="size-3">size 3</li>
    </ol>
    <ol class="scale-3x scale">
      <li class="size-1">size 1</li>
      <li class="size-2">size 2</li>
      <li class="size-3">size 3</li>
    </ol>

    In this case, --scale is defined at the same level as its evaluation, so --size-* will be defined correctly for each case.


    From Specification:

    In the first case you're stuck at 3 because no value is specified for --scale at the root level. In the last case we are stuck with 2 because we defined --scale at the same level and we have its value.


    In all cases we should avoid any evaluation at the :root level as it is simply not useful. The root level is the top level in the DOM, so all elements will inherit the same value, it is impossible to have different values ​​within the DOM unless we evaluate the variable again.

    Your code is equivalent to this code:

    :root {
      --size-1: calc(1 * 1 * 1rem);
      --size-2: calc(2 * 1 * 1rem);
      --size-3: calc(3 * 1 * 1rem);
    }

    Let’s give another example:

    :root {
      --r:0;
      --g:0;
      --b:255;
      --color:rgb(var(--r),var(--g),var(--b))
    }
    div {
      color:var(--color);
    }
    p {
      --g:100;
      color:var(--color);
    }
    <div>
      some text
    </div>
    <p>
      some text
    </p>

    Intuitively, we might think that we can change --color by changing one of the 3 variables defined at the :root level, but we cannot do this with the above operation code Same as this:

    :root {
      --color:rgb(0,0,255)
    }
    div {
      color:var(--color);
    }
    p {
      --g:100;
      color:var(--color);
    }
    <div>
      some text
    </div>
    <p>
      some text
    </p>

    3 variables (--r, --g, --b) are evaluated within :root code> therefore We have replaced them with their values.

    In this case we have 3 possibilities:

    • Use JS or other CSS rules to change variables within :root. This doesn't allow us to have different colors:

    :root {
      --r:0;
      --g:0;
      --b:255;
      --color:rgb(var(--r),var(--g),var(--b))
    }
    div {
      color:var(--color);
    }
    p {
      --g:200; /*this will not have any effect !*/
      color:var(--color);
    }
    
    :root {
      --g:200; /*this will work*/
    }
    <div>
      some text
    </div>
    <p>
      some text
    </p>

    • Evaluate the variable again within the desired element. In this case we lose any flexibility and the definition inside :root will become useless (or at least will become the default):

    :root {
      --r:0;
      --g:0;
      --b:255;
      --color:rgb(var(--r),var(--g),var(--b))
    }
    div {
      color:var(--color);
    }
    p {
      --g:200;
      --color:rgb(var(--r),var(--g),var(--b));
      color:var(--color);
    }
    <div>
      some text
    </div>
    <p>
      some text
    </p>

    • Change the :root selector to the universal selector *. This will ensure that our functions are defined and evaluated at all levels. In some complex cases this may produce some unwanted results

    * {
      --r:0;
      --g:0;
      --b:255;
      --color:rgb(var(--r),var(--g),var(--b))
    }
    div {
      color:var(--color);
    }
    p {
      --g:200;
      color:var(--color);
    }
    <div>
      some text
    </div>
    <p>
      some text
    </p>


    With this in mind, we should always keep evaluation to the lowest possible point in the DOM tree, especially after a variable has changed (or at the same level)

    This is something we shouldn’t do

    :root {
      --r: 0;
      --g: 0;
      --b: 0;
    }
    .color {
      --color: rgb(var(--r), var(--g), var(--b))
    }
    .green {
      --g: 255;
    }
    .red {
      --r: 255;
    }
    p {
      color: var(--color);
    }
    
    h1 {
      border-bottom: 1px solid var(--color);
    }
    <div class="color">
      <h1 class="red">Red </h1>
      <p class="red">I want to be red :(</p>
    </div>
    <div class="color">
      <h1 class="green">Green </h1>
      <p class="green">I want to be green :(</p>
    </div>

    this is what we are supposed to do

    :root {
      --r:0;
      --g:0;
      --b:0;
    }
    .color {
      --color:rgb(var(--r),var(--g),var(--b));
    }
    
    .green {
      --g:255;
    }
    
    .red {
      --r:255;
    }
    
    p {
      color:var(--color);
    }
    h1 {
      border-bottom: 1px solid var(--color);
    }
    <div class="red">
      <h1 class="color">Red title</h1>
      <p class="color">Yes I am red :D</p>
    </div>
    <div class="green">
      <h1 class="color">Green title</h1>
      <p class="color">Yes I am green :D</p>
    </div>

    We can also do this:

    :root {
      --r:0;
      --g:0;
      --b:0;
    }
    .color {
      --color:rgb(var(--r),var(--g),var(--b));
    }
    
    .green {
      --g:255;
    }
    
    .red {
      --r:255;
    }
    
    p {
      color:var(--color);
    }
    h1 {
      border-bottom: 1px solid var(--color);
    }
    <div class="red color">
      <h1 >Red title</h1>
      <p >Yes I am red :D</p>
    </div>
    <div class="green color">
      <h1>Green title</h1>
      <p >Yes I am green :D</p>
    </div>

    reply
    0
  • Cancelreply