Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Juan Agustín Moyano
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Juan Agustín Moyano ( @jonwincus )

Experimenting With CSS Variable / Custom Property DOM Inheritance

By on

For the last couple of days, Seema Shariat and I have been discussing the concept of theming in Angular applications. And, to be honest, this is something that I know next-to-nothing about. So, I've started to read up on the matter; and it seems that CSS Custom Properties (commonly referred to as CSS Variables) are the new hawtness when it comes to themable styles. In a lot of the articles that I've read on CSS custom properties, there seems to be a lot of confusion around the scoping and inheritance of these values. So, I wanted to setup a small experiment such that I could see how CSS custom properties are inherited by DOM (Document Object Model) node descendants.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

The concept of this demo is simple: I have three nested containers, each of which defines a CSS "color" and "background-color" property value based on a set of CSS custom properties (aka, CSS variables):

.container {
	background-color: var( --background-color ) ;
	color: var( --color ) ;
}

Each of these containers also has several action links. And, by clicking on one of said actions, a CSS custom property definition can be added to or removed from the target container. This will allow us to see how those "DOM local" values are experienced at lower levels of the current DOM tree branch.

In this demo, in order to reduce duplication of values, I'm defining some global CSS custom property values for "Light" and "Dark" theme colors on the :root. Then, as you click in each container, I'm using those shared "constants" as the source-values of the local, dynamic CSS property definitions:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>
		Experimenting With CSS Variable / Custom Property DOM Inheritance
	</title>

	<link rel="stylesheet" type="text/css" href="./demo.css" />
</head>
<body>

	<h1>
		Experimenting With CSS Variable / Custom Property DOM Inheritance
	</h1>

	<p>
		The following <strong>links</strong> Add or Remove
		<strong>CSS Variables</strong> from the contextual container.
	</p>

	<div class="container">
		This is a container:
		<a class="toggle none">None</a>,
		<a class="toggle light">Light</a>,
		<a class="toggle dark">Dark</a>

		<div class="container">
			This is a container:
			<a class="toggle none">None</a>,
			<a class="toggle light">Light</a>,
			<a class="toggle dark">Dark</a>

			<div class="container">
				This is a container:
				<a class="toggle none">None</a>,
				<a class="toggle light">Light</a>,
				<a class="toggle dark">Dark</a>
			</div>
		</div>
	</div>

	<p class="control">
		I am a control case.
	</p>

	<!--
		We're going to define COLOR and BACKGROUND-COLOR CSS variables at the root of the
		document. Then, we're going to consume those variables at the CONTAINER level. At
		each container level, we can then add or remove "branch-local" overrides.
	-->
	<style type="text/css">

		:root {
			--LIGHT-BACKGROUND-COLOR: #f5f5f5 ;
			--LIGHT-COLOR: #333333 ;

			--DARK-BACKGROUND-COLOR: #999999 ;
			--DARK-COLOR: #ffffff ;

			--background-color: var( --LIGHT-BACKGROUND-COLOR ) ;
			--color: var( --LIGHT-COLOR ) ;
		}

		.container,
		.control {
			background-color: var( --background-color ) ;
			color: var( --color ) ;
		}

	</style>

	<script type="text/javascript">

		document.addEventListener( "click", handleClick, false );

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// As the user clicks on the various TOGGLE links, we're going to add and
		// remove CSS Variable values as the given level of the DOM-tree. CSS Variables
		// are inherited just like any other property (which is probably why they are
		// actually called "custom properties", not "variables"). As such, when we define
		// an override on a given DOM element, the override will be implicitly inherited
		// by all of its descendants.
		function handleClick( event ) {

			// If this wasn't a toggle click, ignore it.
			if ( ! event.target.classList.contains( "toggle" ) ) {

				return;

			}

			var toggle = event.target;
			var container = toggle.parentNode;

			// Add the LIGHT OVERRIDE variable values.
			if ( toggle.classList.contains( "light" ) ) {

				container.style.setProperty( "--background-color", "var( --LIGHT-BACKGROUND-COLOR )" );
				container.style.setProperty( "--color", "var( --LIGHT-COLOR )" );

			// Add the DARK OVERRIDE variable values.
			} else if ( toggle.classList.contains( "dark" ) ) {

				container.style.setProperty( "--background-color", "var( --DARK-BACKGROUND-COLOR )" );
				container.style.setProperty( "--color", "var( --DARK-COLOR )" );

			// Remove the override variable values (allowing the current container to
			// inherit the variable values from its DOM parent).
			} else {

				container.style.removeProperty( "--background-color" );
				container.style.removeProperty( "--color" );

			}

		}

	</script>

</body>
</html>

As you can see, when you click into a container, I'm essentially adding or removing the following CSS custom properties to the element's style declaration:

  • --background-color
  • --color

And, if we run this in the browser and toggle the outer-most container to use the "dark" theme and the inner-most container to use the "light" theme, we get the following output:

CSS variable / custom property inheritance in the DOM tree, works just like it would with any other CSS property.

As you can see, when we define CSS custom properties at the outer-most container, we are overriding the values inherited from :root. These override values are then inherited by both the middle container and the inner-most container. However, they are only symptomatic in the middle container because we're also defining CSS custom properties at the inner-most container.

Ultimately, what we can see is that CSS custom properties are inherited just like any other CSS property. Which, in hindsight, is probably why they are documented as "custom properties" and not "variables." This is pretty cool stuff! CSS custom properties are not supported in IE; but, they are supported in Edge, making them available in all "modern" browsers. This is definitely something I'll be learning more about.

Want to use code from this post? Check out the license.

Reader Comments

15,674 Comments

@Edward,

I don't really think it's an either/or. I think both can work really nicely together. For example, LESS / Sass can work really well with all the includes and variables and functions to calculate code at compile time. And, then on top of that, you can use these new CSS custom properties to make things more dynamic at run time. So, I see these as going hand-in-hand.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel