Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Ryan Nibert
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Ryan Nibert

Experimenting With CSS Variable / Custom Property DOM Inheritance

By Ben Nadel 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.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

This is pretty awesome - from my takeaway here do you think it's safe to say that less / sass style preprocessing is the future state for native css?

Reply to this Comment

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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.