CSS animate custom properties/variables

This can be achieved by defining variables using (as of writing this, not well-supported) @property, which allows declaring types and that allows the browser to "understand", for example, that a certain property (variable) is a Number and then it can gradually animate/transition that variable.

Example Code:

@property --opacity {
  syntax: '<number>'; /* <- defined as type number for the transition to work */
  initial-value: 0;
  inherits: false;
}

@keyframes fadeIn {
  50% {--opacity: 1}
}

html {
  animation: 2s fadeIn infinite;
  background: rgba(0 0 0 / var(--opacity));
}

The current types that are allowed include:

length, number, percentage, length-percentage, color, image, url, integer, angle, time, resolution, transform-list, transform-function, custom-ident (an identifier string)


Helpful articles:

  1. https://web.dev/at-property/#writing-houdini-custom-properties
  2. https://css-tricks.com/using-property-for-css-custom-properties
  3. Cool Houdini demos

As stated in the specification:

Animatable: no

and also

Notably, they can even be transitioned or animated, but since the UA has no way to interpret their contents, they always use the "flips at 50%" behavior that is used for any other pair of values that can’t be intelligently interpolated. However, any custom property used in a @keyframes rule becomes animation-tainted, which affects how it is treated when referred to via the var() function in an animation property.


So even if you use opacity with var() in the keyframes it won't animate:

@keyframes test {
  from {
    --one:0;
    opacity: var(--one);
  }
  to {
    opacity: var(--one);
    --one: 1;
  }
}

#test {
  width: 100px;
  height: 200px;
  background-color: black;
}

#test :nth-child(1) {
  width: 20px;
  height: 20px;
  margin: auto;
  background-color: white;
  animation: test 1s  infinite;
  
}
<div id="test">
  <div></div>
</div>

By the way you can make it working if you use it as a transition because in this case you will apply a transtion to the opacity and not the custom property:

#test {
  width: 100px;
  height: 200px;
  background-color: black;
}

#test:hover {
  --one:1;
}

#test :nth-child(1) {
  width: 20px;
  height: 20px;
  margin: auto;
  background-color: white;
  opacity: var(--one,0);
  transition:1s all;
}
<div id="test">
  <div></div>
</div>