CSS --var: inherit(--var) + 2; Yes, you can! Without JS!
Tags: css, tutorial
Published: 2022-07-04 22:48:04

`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](https://github.com/w3c/csswg-drafts/issues/2864). 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.

{% codepen https://codepen.io/propjockey/pen/bffe03c69fa65bec3abcc69cf8ef9059 default-tab=css,result theme-id=40148 %}

Note: No `:has()` or container queries here, this works for [89% of global users](https://caniuse.com/mdn-css_selectors_where) 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:

```css
: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

```css
--hue: calc(inherit(--hue, 0deg) + 30deg)
```

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

```css
--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:

```css
--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