Development icon

Used and Abused – CSS Inheritance and Our Misuse of the Cascade

Micah Godbolt, Developer
#Front-end Development | Posted

We are abusing the CSS inheritance model. There, I said it. I know that the “C” of CSS stands for “Cascading”, but that doesn’t mean we need to cascade from the simplest selector all the way down to a complex component. Too often an h2 starts with a base layer of styles from the root level h2 tag, then gets a second coat applied because it resides in a specific page region. A third application is applied because it belongs to a family of components that share common styles. Lastly, since the h2 belongs to a specific variation of this component, it gets a 4th layer of styles on top of that.

One piece of markup. 4 widely differently sources of styles combined together. Sound convoluted, and crazy? It may be crazy, but it is far from uncommon.

Down The Rabbit Hole

It all starts because someone says “I don’t want to have to write CSS every time I add an h2″. So we write:

h2 {
  color: #fd7900;
  font-size: 1.8em;
  line-height: 1.1;
  margin-bottom: .3em;
}

My Page Titles

This works great at first, but then it happens: A story owner wants to change the look of all of the .sidebar h2.

.sidebar h2 {
  color: #8cc5e6;
  font-size: 2.2em;
  padding-bottom: .2em;
  border-bottom: 1px solid black;
}

My Sidebar Titles

We don’t need to change everything, just the color, size and border. So we take advantage of the CSS cascade and tweak the values that need to be updated.

A closet full of dropping shoes

We are trying to be smart by breaking small pieces of functionality into reusable sidebar blocks, so we decide to create some default block styles. This way all of the blocks we create today, and in the future, can inherit the same styles. Each block has a h2, so let’s look at how that will be styled.

.block
  border: 1px solid #8cc5e6;
  padding: 0 15px;
}
.block h2 {
  /* Color was changed by .sidebar h2, set it back */
  color: #fd7900;
  /* remove border added by .sidebar h2 */
  border: none;
  /* remove the margin set by h2 */
  margin: 0;
  padding: .3em;
  background: #8cc5e6;
  margin: 0 -15px;
}

My Sidebar Block Titles

The rest of the block…

Next Sprint We’re out of Breath

Our blocks are working great. Now we’ve been asked to create an alternate block style that uses the original sidebar header, but has full grey background.

/*

The Markup
<div class="block alt-block">
  <h1>My Title</h1>
  ...
</div>
*/

.alt-block {
  /* Remove border from .block */
  border: none;
  padding: 15px;
  background: #eee;
}

.alt-block h2 {
  /* Need to set color set by .block h2 */
  color: #8cc5e6;
  /* Need to set the padding-bottom back to .sidebar h2 styles */
  padding-bottom: .2em;
  /* Need to set the border back to .sidebar h2 styles */
  border-bottom: 1px solid black;
  /* Remove negative margins and background from .sidebar h2
  but set bottom margin again from root h2 */
  margin: 0 0 .3em;
  /* remove background from .sidebar h2 */
  background: none;
}

My Sidebar Block Titles

The rest of the block…

What have we done?

screenshot of inspection of CSS

There! We’re done! Story is accepted, code has been merged into master. But at what costs? Our .alt-block h2only contains styles that override or revert back to styles we’ve already written.

On top of that, our title is now inheriting styles from 4 different selectors, our code is bloated, our site’s performance is taking a hit, and our style system is incredibly fragile. Any change to h2 , .sidebar h2 , .block h2 will have an effect on .alt-block h2 wether we intended for it to or not.

My Code is Being Repressed

So what exactly just happened? In the above example we experienced 3 different types of CSS inheritance: tag inheritance, location inheritance, and class inheritance. Let’s take a look at how each one of them works, why they cause problems, and solutions to fixing them.

Tag Inheritance

We use HTML tags to add semantic meaning to our sites. When we apply styles to these tags we end up mixing semantics and presentation. I don’t mind applying a very basic set of default styles to our tags, but we ran into problems in our example when we tied our finished, presentational styles to the h2 tag. This not only muddied up the water for all of our other h2tags, but it also made those base styles impossible to reuse once overridden.

Instead of relying on tag inheritance to propagate our base style, we should move those styles into a reusable class or extend. You can even call it .h1 or %h1 if you want to! But the key is to avoid tying presentation to your base tags, and instead move to opt-in typography.

Location Inheritance

I’ve heard this argument many times regarding location inheritance: “I want every h2 in the sidebar to look the same, and I don’t want to have to write CSS every time we create a new sidebar widget”.

This type of thinking goes completely against the mantra of components should be standalone, reusable, and have no knowledge of their parent container. Using location inheritance means that your header styles are dependent on being in the sidebar (not standalone or reusable), and that you’re required have intimate knowledge of your parent container before writing styles.

While I understand the intent behind location inheritance, I have personally seen it fail in many different situations.

  1. Someone is going to ask you to create a new header style for the sidebar and the last thing you want to have to do is try out all 6 of the header tags ( h1 – h6) to see which one has the least number of styles to override.
  2. You will eventually be asked to move a component from the main content region over to the sidebar. How much do you want to bet that the .sidebar h2 styles won’t have ANY effect on the .widget h2 you are moving over.
  3. You might think that using a decendant selector (like .sidebar > h2) will solve all of your problems, but now your styles are dependent on markup order, and there is no way to reuse those h2 styles elsewhere in your site.

In the end, your styles will be more flexible and less prone to breaking if you avoid relying on location inheritance.

Class Inheritance

Class inheritance is the practice of applying an entire system of styles on a collection of markup via a single class. In our example we have a .block class that is a complete component, then we add on another set of styles with .alt-block . In “Object Oriented” terms, this means that .alt-block is tightly coupled to .block, or that it “modifies or relies on the internal workings of another module”. This isn’t always a bad thing, but it is something that can quickly become complex and fragile.

Imagine that you wrote the .block code, and then someone else on the team decided to create .my-calendar-widget using .block as a set of base styles, i.e. a “relied upon module”.

<div class="block my-calendar-widget">
  <h2>My Calendar Title</h2>
  ...
</div>

Unless you have a well established system for documenting these relationships, this becomes what I like to call “hijacked inheritance”. We have no indication in our CSS that .block has another component tightly coupled to it, so we’ll be dealing with random bugs on our calendar page every time we update .block.

The solution to this mess is to forgo class inheritance and instead break the parent class into extendable pieces including the container, titles, text and image treatments. This method is often referred to as composition, and is something I’ll cover in just a few paragraphs.

The Problem Inherited in the System

Inheritance is not unique to CSS. Many object oriented programming languages use the concept of inheritance to pass properties and methods from parent to child. Many of those languages will also go to lengths describing the pitfalls associated with this approach (JavascriptJavaRuby). CSS may not be a programming language like Ruby or Java, but there any many things we can learn from those languages that will help us deal with inheritance in CSS. Let’s take a look at a a few of those lessons below:

Multiple Inheritance

“Avoid multiple inheritance at all costs, as it’s too complex to be useful reliably. If you’re stuck with it, then be prepared to know the class hierarchy and spend time finding where everything is coming from.”

If your components use different combinations classes to achieve their styles (like our .block  and .my-calendar-widget example), it becomes very difficult to anticipate how a change to one component’s CSS might effect your site. Without being able to grep our entire site (which for some CMS’s is nearly impossible), we have no way of knowing which components are using the class we are currently editing, and how that change might interact with other classes they use.

Secondly, we end up spending too much time trying to understand the style hierarchy of an element. Where are the styles coming from? The tag, a class, a parent’s class, the location, the media query, a combination of all of them? When each element has a single source of style application, it is much easier to debug and comprehend the expected behavior of that element.

Composition vs Inheritance

“Use composition to package up code into modules that are used in many different unrelated places and situations.”

Even with single inheritance our .block  element is still completely dependent on the cascade. Move a .block  out of the sidebar and it will break. Change the header to H3  and it will break. The problem is that each element inside of .block  inherits styles from the parent class, the block’s location and the HTML tag itself. No styles are prescribed to it due its semantic purpose, which is to be a “title”.

Composition is the idea that an html component might “contain” other styles, rather than “inheriting” those styles. You can see this principle used extensively in OOCSS, BEM and Sass @extends. Instead of layers of styles, each overriding the last, these approaches embrace the Single Responsibility Principle with hyper targeted selectors that do one job well and one job only.

Putting it all to Work

We’ve spent a great deal of time exploring the different types of inheritance, and how traditional object oriented principles help us identify problems in our inheritance model and find solutions. Now let’s look at how we can use Sass to create this .block element using a compositional approach.

The Markup

Note how each title has its own unique class. This allows us to target the title regardless of the tag used.

<aside class="sidebar">
  <div class="calendar-widget">
    <h2 class="calendar-widget-title">My Calendar Title</h2>
    ...
  </div>
  <div class="latest-blogs">
    <h2 class="latest-blogs-title">My Blog Title</h2>
    ...
  </div>
  <div class="latest-news">
    <h2 class="latest-news-title">My News Title</h2>
    ...
  </div>
</aside>

The Extends

If you are new to Sass, an extend is a tool we can use to propagate a set of styles without applying presentational classes, or duplicating those styles. It is common practice to use silent extends like %primary-header  to keep unused styles out of your stylesheet.

%primary-header {
  font-size: 1.8em;
  line-height: 1.1;
  color: #fd7900;
  margin: .3em;
}

%secondary-header {
  font-size: 2.2em;
  line-height: 1.1;
  color: #8cc5e6;
  margin: 0 0 .3em;
  padding-bottom: .2em;
  border-bottom: 1px solid black;
}

%block-header {
  font-size: 2.2em;
  line-height: 1.1;
  color: #fd7900;
  margin: 0;
  padding: .3em;
  background: #8cc5e6;
  margin: 0 -15px;
}

%block {
  border: 1px solid #8cc5e6;
  padding: 0 15px;
}

%alt-block {
  padding: 15px;
  background: #eee;
}

Styling Each Block

To use one of these extends we simply write @extend%extend-name inside our selector and Sass will take care of the rest.

Notice how each semantic selector is composed of styles rather than inheriting them from its tag, location or parent class. This allows us to “package up our code and reuse it in many different unrelated places and situations”.

.calendar-widget {
  @extend %block;
}
.calendar-widget-title {
  @extend %block-header;
}
/*...*/

.latest-blogs {
  @extend %alt-block;
}
.latest-blogs-title {
  @extend %secondary-header;
}
/*...*/

.latest-news {
  @extend %alt-block;
}
.latest-news-title {
  @extend %secondary-header;
}

Compiled CSS

In our compiled CSS you’ll see that the silent extend %secondary-header is replaced with the 2 classes that extended it. You’ll also notice that we never ended up using the %primary-header , so those styles were omitted. That is one of the advantages of silent extends.

.latest-blogs-title,
.latest-news-title {
  font-size: 2.2em;
  line-height: 1.1;
  color: #8cc5e6;
  margin: 0 0 .3em;
  padding-bottom: .2em;
  border-bottom: 1px solid black;
}

.calendar-widget-title {
  font-size: 2.2em;
  line-height: 1.1;
  color: #fd7900;
  margin: 0;
  padding: .3em;
  background: #8cc5e6;
  margin: 0 -15px;
}
.calendar-widget {
  border: 1px solid #8cc5e6;
  padding: 0 15px;
}

.latest-blogs,
.latest-news {
  padding: 15px;
  background: #eee;
}

My Calendar Title

The rest of the Calendar

My Blog Title

The rest of the blogs

My News Title

The rest of the news

A Single Source of Styles

screenshot of css inspection

In this CSS we recognized that .latest-blogs-titleshared a likeness with .latest-news-title and we connected them through composition.

Will this bloat our CSS?

One popular complaint of extends is that it produces larger CSS files than other approaches. An OOCSS or BEM approach would be very similar to what we have done above except that .alt-block  and .secondary-header  classes would be applied directly to the markup rather than extended via Sass. While it’s true that this approach would save us some bytes, the benefit of extends, and what it does for the longevity of a project, far outweigh a little duplication. Ben Frain put it perfectly a few weeks ago when he said:

“For me, a larger total CSS codebase, made up of components that are in many respects insulated from one another, is preferential to a smaller CSS codebase made up of inter-dependent and intrinsically related styles.”

If we apply these presentational classes directly to the markup our blog title and news title will become inter-dependent and intrinsically related. That is to say, we cannot change one of them without changing the other.

What is old is new again

I don’t come from a traditional programming background, so this has been the first time I’ve had a chance to dive into the difference between inheritance and composition. Being such a well explored topic in languages like Java and C++ I often found myself reading articles that were almost as old as the web itself! The amazing thing about those articles was that they were 100% relevant even 16 years later. From “Inheritance versus composition: Which one should you choose?“:

“In an inheritance relationship, superclasses are often said to be “fragile,” because one little change to a superclass can ripple out and require changes in many other places in the application’s code.”

This is exactly the issue we run into when changes to h2 would ripple down to our .block h2 and cause regressions in our code.

These ideas are not new, but as the web matures, and the complexity of our projects grow, they become more and more relevant to our industry. So for me and my code, I’m going to spend more time reading about other languages, and similar problems  were solved a dozen years ago that we are now facing today with CSS.  

Micah Godbolt

Developer