CSS Variables

Variables in Motion

A cat looking at a computer screen

We’ve said multiple times at this point that CSS Variables lack assigned Type and/or Context. So using CSS animation or transition properties to animate CSS variables is a bit of a challenge.

Out of the Box

As long as we are animating/transitioning a standard CSS property, we can use a CSS Variable as the value of the property.

:where(html) {
   --opacity-0: 0;
   --opacity-100: 1;
}

@keyframes fade-in {
   from {
      opacity: var(--opacity-0);
   }
   to {
      opacity: var(--opacity-100);
   }
}

.button {
   --_button-background-color:       var(--button-background-color, var(--color-purple-800));
   --_button-color:                  var(--button-color, var(--color-mono-0));
   background: var(--button-background-color, var(--color-purple-800));
   border: 2px solid var(--button-border-color, var(--color-purple-600));
   color: var(--button-color, var(--color-mono-0));
   font-size: var(--button-font-size, 1.75rem);
   padding-block: 0.75rem;
   padding-inline: 1.5rem;
   transition:
      background var(--duration-gentle) var(--ease-out-3),
      color var(--duration-gentle) var(--ease-out-3);

   &:where(:hover, :focus-visible) {
      background: var(--_button-color);
      color: var(--_button-background-color);
   }
}

.button--flash {
   animation: fade-in var(--duration-gentle) var(--ease-out-3) alternate infinite;
}

.button__icon {
   --button-color: var(--color-amber-400);
   color: currentColor;
}

What will not work is animating/transitioning the CSS Variable itself…

Except NOW IT WILL!

Spit Take

Introducing the @property Rule

The newly fully supported (July 2024) @property rule allows us to take our standard css variable and transform it into what Google’s Una Kravet’s calls an Advanced Custom Property:

@property --color-accent {
   syntax: '<color>';
   inherits: false;
   initial-value: hotpink;
}

We can now assign Type, Initial Value and Rules of Inheritance to our properties.

@property --card-bg {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0bae8;
}

@property --shine-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #ffbbc0;
}

@property --shine-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0aecb;
}

@keyframes animate-color-1 {
  from {
    --shine-1: initial;
  }
  to {
    --shine-1: var(--color-orange-500);
  }
}

@keyframes animate-color-2 {
  from {
    --shine-2: initial;
  }
  to {
    --shine-2: var(--color-pink-400);
  }
}

.card {
   background: radial-gradient(
      300px circle at 55% 60% in oklab,
      var(--shine-2), transparent 100% 100%
   ), radial-gradient(
      farthest-side circle at 75% 30% in oklab,
      var(--shine-1) 0%, var(--card-bg) 100%
   );
   animation: animate-color-1 3s infinite linear alternate, 5s animate-color-2 1s infinite linear alternate;
}