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:

:where(
.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
}
:where(
.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!

Linkage!

Docs: https://propjockey.github.io/css-inherit-fn/
Live Demo Playground: https://propjockey.github.io/css-inherit-fn/#live-examples
Package: https://www.npmjs.com/package/css-inherit-fn
Repo: https://github.com/propjockey/css-inherit-fn
Twitter: https://twitter.com/Jane0ri

Discussion (3)

Collapse
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.

W3.org 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.

Collapse
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!

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

testing

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