This slide deck was created for use in a controlled environment, during a talk. It works best with Chrome. It may or may not work properly in other browsers. The demos were live coded, so these slides are a bit pointless if you never watched the talk.

CSS (Variable) Secrets

By Lea Verou (@LeaVerou)

Picture of me
Hola, soy Lea


I grew up in Lesbos, Greece

…which technically makes me geographically Lesbian

I make stuff

CSS WG Invited Expert

MIT HCI researcher @ CSAIL

CSS Secrets by O’Reilly ★★★★★ on Amazon

¿¡CSS Variables?!

$color: #ff0066;

@color: #ff0066;


--color, --corner, inline style, fallback

CSS is awesome

CSS Variables work like
normal CSS properties

inheritance, cancel inheritance, inherit keyword

CSS Variables are
inherited properties
but you can change that

					--img: "sad.jpg";
					background: url("img/" var(--img));
CSS limitation

					--img: "img/sad.jpg";
					background: url(var(--img));
CSS bug

					--img: url("img/sad.jpg");
					background: var(--img);

CSS variables + url() =💩


  • --foo:; is invalid
  • --foo: ; is valid
  • --foo--FOO
No CSS variables support
No --accent-color set
--accent-color: yellowgreen
--accent-color: 42deg

			background: red;
			background: var(--accent-color, orange);

Invalid at computed-value time?!

Invalid at computed-value time = initial

			var(--color1, var(--color2, var(--color3, red)))
CSS is awesome
CSS is declarative

Cycles make variables invalid
at computed-value time

			--accent-color: 42deg;
			--accent-color: var(--accent-color);
			background: red;
			background: var(--accent-color, orange);

Won’t someone, please, think of browser support?!

Chrome Firefox Edge Safari
CSS Variables 49 31 15 9.1
Chrome Firefox Edge Safari
CSS Variables 49 31 15 9.1
@supports 28 22 13 9

Show division not working

Variable values are token lists

Number → unit: calc(var(--foo) * 1px)
Unit → number:

Use variables for pure data,
not CSS values

Try to animate --color | show that var() is ok

CSS Variables in @keyframes?

[CSS variables] can even be tran­sitioned or ani­mated, but since the UA has no way to interpret their con­tents, they always use the "flips at 50%" behav­ior that is used for any oth­er pair of val­ues that can’t be intelligently interpolated.

CSS Custom Properties for Cascading Variables Module Level 1

CSS variables + animations =💩

🔮 Near Future 🔮

				name: "--color",
				syntax: "<color>",
				initialValue: "black",
				inherits: false
Chrome Firefox Edge Safari
CSS.registerProperty() ⛳️

CSS variables + animations =🔮♥️

Common use Cases

Theming by class, by style, box-shadow transition | default default values

CSS Variables enable theming
independent of CSS structure

Default default values are possible

			--colorD: var(--color, black);
			/* use --colorD instead of --color internally */

CSS Variables make
responsive design easier

Cool use cases

CSS Variables enable you to
set multiple properties at once

🎶 Laughing in the purple rain 👬☔️

CSS Variables let you create
single property mixins
(like function currying, in programmerese)

No inherit from box-shadow, invalid at computed value time

CSS Variables enable you to
create custom longhands

inline style too

CSS Variables enable you to
define your own properties
(in some cases)

CSS Variables & SVG

CSS variables + SVG =♥️

CSS Variables & JavaScript

			// Get variable from inline style"--foo");

			// Get variable from wherever

			// Set variable on inline style"--foo", 38 + 4);

			var root = document.documentElement;

			document.addEventListener("mousemove", evt => {
				let x = evt.clientX / innerWidth;
				let y = evt.clientY / innerHeight;"--mouse-x", x);"--mouse-y", y);


<html mv-app
      style="--mouse-x: [$mouse.x / innerWidth];
	          --mouse-y: [$mouse.y / innerHeight];">

			for (input of document.querySelectorAll("input")) {"--value", input.value);

			document.addEventListener("input", evt => {
				var input =;"--value", input.value);

			for (let el of document.querySelectorAll(".scrolling")) {
				el.addEventListener("scroll", evt => {
					let maxScroll = el.scrollHeight - el.offsetHeight;
					let scroll = el.scrollTop / maxScroll;"--scroll", scroll);

The allowed syntax for custom properties is extremely permissive. The <declaration-value> production matches any sequence of one or more tokens, so long as the sequence does not contain <bad-string-token>, <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>, or top-level <semicolon-token> tokens or <delim-token> tokens with a value of "!".

In addition, if the value of a custom property contains a var() reference, the var() reference must be valid according to the specified var() grammar. If not, the custom property is invalid and must be ignored.

The values of custom properties, and the values of var() functions substituted into custom properties, are case-sensitive, and must be preserved in their original author-given casing. (Many CSS values are ASCII case-insensitive, which user agents can take advantage of by "canonicalizing" them into a single casing, but that isn’t allowed for custom properties.)

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().

Custom properties are ordinary properties, so they can be declared on any element, are resolved with the normal inheritance and cascade rules, can be made conditional with @media and other conditional rules, can be used in HTML’s style attribute, can be read or set using the CSSOM, etc.

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.

CSS Variables are a revolution for
separation of style and behavior