Skip to main content

An Interesting Limitation of CSS Custom Properties

By Tyler Sticka

Published on November 17th, 2022

As soon as I read my teammate Paul’s explanation of The Math Behind Nesting Rounded Corners, I wanted to recreate it using custom properties and calc.

I thought I’d be able to do something like this:

:root {
  --radius: 1em;
  --padding: 0.375em;
}

.container {
  border-radius: max(var(--radius), 0em);
  padding: var(--padding);
}

.container .container {
  --radius: calc(var(--radius) - var(--padding));
}

But that doesn’t work. It creates what the Custom Properties specification calls a “dependency cycle,” where the value of --radius is dependent on itself.

I tried working around this using CSS counters, but as of this writing those don’t work with calc. I also tried setting --radius using @property with inherits: true, but the result was the same as before.

So best solution I’ve come up with is to manually iterate a separate property:

:root {
  --radius: 1em;
  --padding: 0.375em;
}

.container {
  border-radius: max(calc(var(--radius) - var(--padding) * var(--depth, 0)), 0em);
  padding: var(--padding);
}

.container .container {
  --depth: 1;
}

.container .container .container {
  --depth: 2;
}

.container .container .container .container {
  --depth: 3;
}

Here are those styles in action:

It’s kind of a bummer having to guess how many levels of nesting you’ll need, but it should be manageable for smaller values like border radii.

There’s already some talk of new CSS features that would simplify this sort of thing. I’m sure we’ll have a more elegant solution sooner or later.

Comments

Jakub Jankiewicz said:

This is a typical misunderstanding form people that use JavaScript (or any other programming language). Custom Properties (CSS variables) don't create something like JS scope. Every variable has only one instance. CSS inheritance is nothing like JavaScript scope.

Reply to Jakub Jankiewicz

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.

Replies to Jakub Jankiewicz

Tyler Sticka (Article Author ) replied:

That’s probably true. In my case, the main reason I assumed this would be possible is because of how relative font-size values work. If I wanted each nested container to set its font-size to the parent’s minus padding, that’s trivial:

.container .container {
  font-size: calc(1em - var(--padding));
}

I rely on this capability so often that I forget how unique it is. I hope proposals like an inherit() function or additive CSS will eventually change that.

Jane Ori said:

I wrote less and sass inherit(--var) functions to make this possible:
https://codepen.io/propjockey/pen/rNdOWGB

Reply to Jane Ori

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.

Replies to Jane Ori

Tyler Sticka (Article Author ) replied:

That's a very clever trick!

It suffers from the same limitation as my example, which is needing to know the maximum depth ahead of time. And the output CSS can be quite a bit larger given the size of those selectors. But the inherit(...) syntax is a lot friendlier than a multiplier for certain transformations. Thank you for sharing!

Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.