Specificity in CSS

Hero image for Specificity in CSS. Image by KOBU Agency.
Hero image for 'Specificity in CSS.' Image by KOBU Agency.

CSS is a beautiful, pragmatic, and convoluted language. Easy to use and getting more and more versatile every day, modern websites are typically beautified by thousands if not hundreds of thousands of lines of CSS.

With all of that code in play, though, there are bound to be some sticky situations. If you're adding a new element or a new style to an existing website, you will probably run into issues where you need to overwrite parts of the preexisting styling code. This is where specificity comes into play.

There are a wide number of ways to target elements with CSS, and each way has its pros and cons as well as differing levels of specificity. With a little forward planning, you can make your code maintainable for the foreseeable future, and avoid having to resort to some of the jankier methods.


The Cascade

As a new frontend developer, the cascade is one of the first things you need to get your head around and understand fully in order to write performant CSS.

CSS (or Cascading Style Sheets) can be very much a doeswhatitsaysonthetin type of language. By that, I mean that the "cascade" is what you need to consider when you're overwriting styles. The cascade algorithm takes into account a whole lot of things, such as where the stylesheet is coming from (user agent, user, author, etc.), as well as looking at the order of selectors, how selectors are combined, whether you're targeting elements, classes or IDs, and whether that rule is followed by an !important declaration.

For today, we are going to focus on the cascade within an author stylesheet; this is the most likely scenario you will find yourself in (and spend most of your time working within) as a frontend developer.


The Good

Let's start out by checking out some examples of good specificity, and ways of targeting elements, and applying styles that can help you build an easily maintainable and clean codebase for the future.

Selector Order

First things first the order of your selectors makes a difference. Earlier selectors are overwritten by later ones that are the same, e.g:

p {  color: red;}p {  color: blue;}

In the above, because we read CSS toptobottom, all <p> tags on the page will (unless styled otherwise) be blue, and not red.

At first glance this may not appear to be particularly useful after all, why would you target all paragraphs once, and then do it again separately? That's a fair point; I don't think I've ever had to do that on any project, and it would be weird to do so. In fact, I would go as far as to say: I really don't think you should ever style an element on your page by its element type.

However, this is something important to bear in mind when working with legacy projects, or when taking over an existing codebase. If your CSS isn't applying to an element, check whether there's something else affecting it further down the files especially if you're working with multiple SCSS files being compiled into one minified CSS file.

The next thing to bear in mind is that this doesn't just have to be the exact same selector in order for one to overwrite the other. The order also makes a difference in this example:

.red {  color: red;}.blue {  color: blue;}.green {  color: green;}

Even if an element has all three of these classes applied to it, the text will come out green because of the order of the selectors within the stylesheet. This is an example that's much more relevant to working on a new project, and far more likely to trip you up.

Class Targeting

Here's where we start to get useful when moving forwards into a new project build. If you want to override style rules targeted at elements, you can do it by targeting classes, like this:

a {  text-decoration: none;  font-weight: 800;  color: blue;}.red {  color: red;}

Now, I stand by what I said above about targeting elements via their element name, but this helps to demonstrate a point. Targeting a class is a great way to override global element style rules. Here we've set all links to have no underline, be emboldened, and be blue but any links (or elements) which also have the .red class name will show text in red, instead. This can be handy for dynamic content, where you allow clients to put links in different places or apply classes to them.

Chaining and Combining Selectors

Part of the way that the cascade algorithm works is by looking at combined or chained selectors. For instance, take this example:

.nav-link {  font-weight: 400;}.nav-link.active {  font-weight: 800;}

Here, we're dealing with making the nav link to our current page show up in bold. By chaining the class selectors together (.navlink and .active), we've upped the specificity and now the link to our current page will show up in bold.

This would also work if the order was reversed and the .navlink.active came first, which means that chaining and combining selectors can overwrite selector order in the cascade; chained selectors are more specific and have a higher specificity than selector order.


The Bad

Now that we've covered some common and pretty good ways to apply styles, let's take a look at some ways that will start causing problems for you down the road.

Targeting by Id

In terms of specificity, targeting by ID is very high indeed, being more specific than class selectors entirely (whatever the order they fall in). To overwrite class targeting, you can target by ID. Here's an example:

a {  text-decoration: none;  font-weight: 800;  color: blue;}.red {  color: red;}#green {  color: green;}

If our anchor element had both a classname 'red' and an ID 'green', then green would win out.

In a pinch, this is fine although when reviewing your code, I'm going to want to know exactly why there is no other option available. Targeting one very specific element by ID isn't going to ruin your entire codebase, obviously. However, when you start using IDs throughout your stylesheets, you're going to run into issues when it comes time to overwrite these styles. Because of CSS specificity, to 'beat' an ID selector, you would have to chain ID and class selectors, resulting in evergrowing selectors and ultimately spaghetti code.


The Ugly

Uhoh. This is a bad place to be. In our code review, we're likely revisiting anything you've written that falls into this category...

!important

I've written about using !important before, and they do have their (limited) uses. For instance, if you're working on an ancient website or the stylesheets have been compiled from SCSS files that are no longer accessible, and you need to put a quick fix in place before rebuilding the project as a whole. Or, perhaps, if you are really stuck overwriting the styling brought in from an external source like an external library. I'm sure we've all been there.

Nevertheless, the situations where !important could (or should) conceivably be used are minuscule. If you're using important tags throughout your stylesheets as you write them, then you are setting yourself (and the rest of your team) up for a world of pain when it comes time to refresh parts of the website and carry out updates. Take our example from the first point, but let's include an important tag:

.red {  color: red !important;}.blue {  color: blue;}.green {  color: green;}

Now, anything with a red class will show red text regardless of whether that element also has blue, or green classes. There's only one thing that reliably beats out the important tag, and that's where we headed for our final stop...

Inline Styles

In the world of CSS development, inline styles are nasty. I will admit, I do occasionally use inline styles but like everything we discuss here today there is a time and a place for them. For example, when toggling specific styles within a React Component (i.e., where you need to calculate or change background positions based on external factors).

When it comes to coding web apps or websites, there's certainly a view that it's important to keep your structural, logical and presentational code completely separate. In recent years with the rise of React though, inline style declarations are becoming more ubiquitous again.

Outside of very specific use cases, this can be a nightmare. If you need to make a change across hundreds of elements during say a brand refresh, you are going to have to go to each component and update the code in there. You might be able to write your code in a way that limits how painful this process will be by making use of reusable components, but it's still going to be less efficient than working with a separate CSS file.

Inline styles also completely invalidate the cascade, meaning anything in your CSS files that targets that same element will be overwritten potentially meaning that to update the styles of an element, you'll need to check out the stylesheet and wherever your inline styles are being injected from as well.


The Wrap‑Up

Ultimately, CSS works due to the way it cascades. It's an efficient, effective and intuitive system, and can allow for clean, DRY, and easytomaintain code. However, used badly, it can definitely result in a world of headaches for any developer inheriting your project or even for your future self.


Categories:

  1. CSS
  2. Development
  3. Front‑End Development