11

For the html:

<body>
<div>
  <div>
    <div>
      ...
    </div>
  </div>
</div>
</body>

Are there any ways to create a recursive variable that uses its parent's value:

body > div {
    --x: 1;
}

div {
    --x: calc(var(--x) + 1);
}

The above is not valid because css variables cannot have dependency cycles. Another invalid example:

body > div {
    --is-even: 0;
    --is-odd: 1;
}

div {
    --is-even: var(--is-odd);
    --is-odd: var(--is-even);
}

Are there any indirect ways to express such recursive variables in css?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Matt Bierner
  • 58,117
  • 21
  • 175
  • 206
  • 1
    Have you looked into css counters? https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters – Denno Apr 23 '18 at 05:28
  • Are you sure you can even do `calc(var(--x) + 1);`? If I make a fiddle with a non-cyclic dependency like `body {--x:1}` and `div {--y: calc(var(--x) + 1)}` then --y comes out as 0. – Mr Lister Apr 23 '18 at 07:02
  • Wait, not in all browsers. Need to check more, stand by. – Mr Lister Apr 23 '18 at 07:05
  • Counters work great for text but you can't currently use them as number property values. There's mention of [`counter-value` in the css draft](https://drafts.csswg.org/css-lists-3/#counter-functions) that would support this, although it hasn't even reached draft stage yet – Matt Bierner Apr 23 '18 at 15:51

2 Answers2

11

You can use two CSS variables to simulate the recursive behavior and avoid cycle dependency.

Here is an example:

body {
  --x: 10;
}
.y {
  --y: calc(var(--x) + 1);
}
.x{
  --x: calc(var(--y) + 1);
}
.result {
  border-right:calc(1px * var(--y)) solid red;
  border-left:calc(1px * var(--x)) solid green;
  height:50px;
}
<body>
  <div class="y">
    <div class="x">
      <div class="y">
        <div class="x">
          <div class="y">
            <div class="x">
              <div class="y result">

              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

If you inspect the element you will find for the last element that border-right is equal to 17px (10 + 7) and border-left is equal to 16px (10 + 6)

enter image description here

This idea fits nicely in elements with a 2 level structure, like lists:

body {
  --x: 30;
}

ul { 
    font-size: calc(var(--x) * 1px);
    --y: calc(var(--x) - 8);
}

li {
  --x: calc(var(--y));
}
  <ul>level A
    <li>item 1
    </li>
    <li>item 2
      <ul>level B
        <li>item 2.1
          <ul>level C
            <li>item 2.1.1
            </li>
            <li>item 2.1.2
            </li>
          </ul>
        </li>
        <li>item 2.2
        </li>
      </ul>
    </li>
  </ul>
vals
  • 61,425
  • 11
  • 89
  • 138
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
1

If you can't add intermediate elements (divs with alternate classes or ul/li) then it seems that the simplest approach is to set variable manually:

div { --x: 1 }
div div { --x: 2 }
div div div { --x: 3 }
/* ... */

In any case a document will have a limited nesting, say 20, 50, 100 levels. Not thousands.

Here is also an article on self referencing variables. The author proposes the following approach:

:root {
  --x: 1;
}
div {
  --mixin: {
    --x: calc(var(--x) + 1);
  };
  @apply --mixin;
}

However he says the following:

Currently you need Google Chrome with the experimental web plattform features flag enabled to see it working.

UPDATE: Jan 2019 CSS @apply was discarded, partly because of some problems it has with applying mixins that themself include other variables. So we have to wait until parent-var() becomes a thing in CSS.

So for now the following options are available:

  1. Define variable manually for each level
  2. Use two variables for alternating elements
  3. Wait for parent-var()
Denis
  • 1,167
  • 1
  • 10
  • 30