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
}
How it works - The Odd / Even var() Chain
What we wrote originally is
--hue: calc(inherit(--hue, 0deg) + 30deg)
Then, inside the odd-nesting selector, the vanilla CSS becomes:
--hue: calc(var(--io1_hue, 0deg) + 30deg);
--io2_hue: var(--hue);
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);
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
Top comments (3)
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.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!
testing
Some comments have been hidden by the post's author - find out more