DEV Community

Cover image for CSS --var: inherit(--var) + 2; Yes, you can! Without JS!
Jane Ori
Jane Ori

Posted on

CSS --var: inherit(--var) + 2; Yes, you can! Without JS!

inherit(--var) or parent(--var) is a long desired CSS feature which has been discussed by the CSS Working Group and is tracked with an open issue in the csswg-drafts repo. Their discussion seems to be leaning towards only allowing direct > descendants to pull value from the direct parent, which is still very useful.

What I'll be demonstrating today works beyond direct parents though, with one small caveat: the source and recipient elements will need a common selector (both div, or both .whatever, for example) to facilitate the handoff. So no backwards compatible transpiling for where their discussion stands now...

The easy way - with mixins

Hit the ground running with a reusable Less (or Sass) mixin to generate the CSS for us.

Note: No :has() or container queries here, this works for 89% of global users at time of writing. Our limiting factors are the :where() selector and Level 4 :not() selector. 🎉

At the root .hue element, --hue evaluates to 0deg because it uses the fallback in inherit(--hue, 0deg). From there, every .hue inherits the previous value and adds 30deg to it.

How it works - The Selectors

The core of this idea is that .sel:not(.sel .sel) will only select .sel that isn't nested in another .sel element.

Then .sel .sel:not(.sel .sel .sel) will only select a .sel that is a descendant of exactly one ancestor .sel but no more.

Do that up to a maximum depth...
then split them into two groups to set up the next part of this trick:
odd and even.

Finally, wrap them in a :where() to drop the specificity of the selector to 0 so anything else we write in our CSS will override it without specificity wars. (Most selectors in most CSS libraries will be wrapped in :where() in the future so you never have to worry about developer users fighting your selectors.)

In our compiled codepen, the .hue selector, up to a maximum nesting of 12, winds up looking like this:

.hue:not(.hue .hue),
.hue .hue .hue:not(.hue .hue .hue .hue),
.hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue)
) {
  ... // odd-nesting code
.hue .hue:not(.hue .hue .hue),
.hue .hue .hue .hue:not(.hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue),
.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue:not(.hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue .hue)
) {
  ... // even-nesting code
Enter fullscreen mode Exit fullscreen mode

How it works - The Odd / Even var() Chain

What we wrote originally is

--hue: calc(inherit(--hue, 0deg) + 30deg)
Enter fullscreen mode Exit fullscreen mode

Then, inside the odd-nesting selector, the vanilla CSS becomes:

--hue: calc(var(--io1_hue, 0deg) + 30deg);
--io2_hue: var(--hue);
Enter fullscreen mode Exit fullscreen mode

What this is doing is looking for an --io1_hue var that only exists on even-nesting (but is automatically inherited because it's a CSS var). So at the root .hue, if we haven't overwritten --hue, it uses the 0deg fallback.

Then, it creates an odd-nesting export of --hue by creating and setting the --io2_hue var to --hue.

And, inside the even-nesting selector, the vanilla CSS becomes:

--hue: calc(var(--io2_hue, 0deg) + 30deg);
--io1_hue: var(--hue);
Enter fullscreen mode Exit fullscreen mode

Which does the exact same thing, except now inherit(--hue became var(--io2_hue which is looking for the export from our odd-nesting depth.

Finally, the chain is complete when we set the --io1_hue export for the next odd-nesting to consume.


That's it! Have fun!

And we don't have to think about any of the details because the inherit() abstraction just makes sense and it's all we have to write now! <3

If you enjoy this or make any useful or fun demos, please do tweet @ me!


Live Demo Playground:

Discussion (3)

abhinav1217 profile image
Abhinav Kulshreshtha

Nice writeup. I haven't used less in years, it is nice to see some preprocessor content.

A simpler, pure css way of Odd and Even would be :nth-child(2n+1) :nth-child(2n+2).
Ideally, simple 2n should work for even rows but 2n+2 has been quite popular due to some reason I don't know about. article on even-odd uses col:first-child col:nth-child(2n+3) it still works as expected and I have used it a few times in project.

janeori profile image
Jane Ori Author

Unfortunately those are even odd sibling selectors, inherit() needs even odd depth selectors, so it's a little bit more work. Thank you for checking it out though!

loudacris115 profile image
Info Comment hidden by post author - thread only accessible via permalink


Some comments have been hidden by the post's author - find out more