Better CSS

Lesson 1: Specificity

Spe­cificity is the fun­da­ment­al mech­an­ism that CSS uses to decide which styles are more import­ant than others.

If not man­aged prop­erly, spe­cificity can quickly spir­al out of con­trol and cause future updates to take twice as long.

In this les­son we’re going to cover:

How spe­cificity works #

Let’s say you have two declar­a­tions which affect the col­or of a h1 ele­ment. Your browser needs some way of decid­ing which one to render and which one ignore.

See the Pen  ZEGByQM by Joe For­shaw (@joeforshaw) on Code­Pen.

Here the color: blue style has won, but how did your browser decide to ignore color: red? The ele­ment matches both declar­a­tions, so what’s going on?

Let’s talk spe­cif­ics #

Spe­cificity says that declar­a­tions which tar­get spe­cif­ic ele­ments should be chosen over declar­a­tions that gen­er­ally style a wide scope of ele­ments. It allows us to define a set of unspe­cif­ic default rules that can be gradu­ally over­rid­den by more spe­cif­ic ones for our indi­vidu­al UI components.

Or put anoth­er way:

The wider the scope of ele­ments matched by a declar­a­tion, the lower it’s specificity.

So in terms of CSS code, what does that mean? Going from least spe­cif­ic to highest, here are the dif­fer­ent​“tiers” of specificity:

  1. The Uni­ver­sal Select­or (e.g. *) affects every ele­ment, there­fore has no spe­cificity. Styles defined with a * select­or can be over­riden by any oth­er declaration.
  2. Type Select­ors (e.g h1, div) affect all ele­ments of a giv­en type, there­fore have just a small amount of specificity.
  3. Class Select­ors (e.g. .container), Attrib­ute Select­ors (e.g. [type="radio"] and Pseudo-Class Select­ors (e.g. :hover) have the same spe­cificity. All are more spe­cif­ic than type selectors.
  4. ID Select­ors have even high­er spe­cificity. An ID is sup­posed to uniquely identi­fy a single ele­ment on a page, so it makes sense that a style tar­get­ing that element’s ID is very specific.
  5. Inline styles (e.g. style="color: red") will over­ride any rules defined in extern­al stylesheets. They’re even more spe­cif­ic than ID selectors.
  6. Styles with an !import­ant tag (e.g. color: red !important) will over­ride any oth­er declar­a­tions, includ­ing inline styles. The nuc­le­ar option.

There’s a few things to remem­ber here, but if you focus on the prin­ciple of scope vs. spe­cificity you’ll find it easi­er to recall what trumps what.

Example #

Let’s see an example of spe­cificity in action:

See the Pen  gOpLyXj by Joe For­shaw (@joeforshaw) on Code­Pen.

It all seems to be work­ing per­fectly! There are a few more nuances that com­plic­ate things however…

Multi-part declar­a­tions #

Declar­a­tions that con­tain a mix of types, classes, IDs, etc will increase their spe­cificity when com­pared against oth­er declar­a­tions in the same spe­cificity tier.

For example, #header would be over­rid­den by div#header because it’s more spe­cif­ic. How­ever, #header wouldn’t be over­riden by div.header because both type and class select­ors belong to lower spe­cificity tiers than ID selectors.

A com­mon way of cal­cu­lat­ing spe­cificity is done by assign­ing scores to declarations:

In this setup, 1,0,0,0 > 0,1,0,0 > 0,0,1,0 > 0,0,0,1. When we mix and match, we increase the digit of the score that cor­res­ponds to each selector’s spe­cificity tier. 

In this second setup, 0,1,0,1 > 0,0,1,1 > 0,0,0,10 — i.e. div#header is the most spe­cif­ic. Des­pite there being ten div tags in the first select­or, div.header would still be more spe­cif­ic because the class select­or belongs to a high­er spe­cificity tier.

Under­stand­ing the above pro­cess is key to mak­ing spe­cificity a friend instead of a foe. We’ll explore how a little later.

Spe­cificity ties #

When two declar­a­tions have equal spe­cificity and tie, the rule that is loaded last by the browser will be the one that gets chosen and rendered.

See the Pen  VwLmOOJ by Joe For­shaw (@joeforshaw) on Code­Pen.

We’ll explore this aspect of spe­cificity a bit more later, as it can become really use­ful when your code­base start to grow.

What is bad spe­cificity? #

If you’ve worked in any reas­on­­ably-sized stylesheet code­base, you’ll know firsthand what bad spe­cificity looks like. A strew of !important tags, styles on arbit­rary IDs and unneeded div wrap­pers whose whole pur­pose is to nudge the spe­cificity just a bit high­er than oth­er exist­ing styles.

Future updates are made harder and harder with every increase in specificity.

A single ill-judged, high-spe­­ci­ficity declar­a­tion that matches a wide scope of ele­ments (e.g. div { background-color: red !important; } 😵) can force every sub­sequent update to dance around it. It can dom­in­ate a code­base and become dif­fi­cult to remove. The only options are to carry on the dance, or to per­form a poten­tially dif­fi­cult extrac­tion. The lat­ter may involve unpick­ing every change that happened in the inter­im that relied on the bad declar­a­tion. Not an easy task whatsoever.

Graph it out #

To get insight on how well spe­cificity is being man­aged in your code­base, plot a graph of your declar­a­tions’ spe­cificity scores against their loc­a­tion in your stylesheets. There’s one or two good online sites out there that can do this for you.

If spe­cificity has been man­aged well, the graph will be a smooth low line that slopes slightly upwards towards the end. In real terms, this means your low­est spe­cificity declar­a­tions are being defined first and your highest last. Few­er declar­a­tions are being over­writ­ten redundantly.

Good spe­cificity graph #

If spe­cificity hasn’t been man­aged well, the graph will be a spikey one that runs con­sist­ently high. This indic­ates that there’s lot of high-spe­­ci­ficity rules. Declar­a­tions loaded after spikes will likely have to battle to over­ride the​“spikey” declar­a­tions, rais­ing gen­er­al spe­cificity further.

Fix­ing bad spe­cificity #

Fix­ing an exist­ing code­base full of high-spe­­ci­ficity declar­a­tions can be tricky. The idea is to gen­er­ally lower spe­cificity so that new styles aren’t required to be very spe­cif­ic to over­ride exist­ing ones.

Start with the spikey declar­a­tions that appear­ing earli­er in your stylesheets. These are often the trouble­some ones because styles that appear after them which tar­get the same ele­ments have to match their specificity.

If you’re deal­ing with high-spe­­ci­ficity declar­a­tions which affect a lot of ele­ments, you’ve got to be par­tic­u­larly care­ful. Before doing any­thing rash, try to list out every­where that could be impacted by a change to one of these declarations.

Avoid­ing bad spe­cificity #

Avoid­ing a code­base full of high-spe­­ci­ficity declar­a­tions is much easi­er than fix­ing one. The gen­er­al rule of thumb is to keep spe­cificity as low as pos­sible. A few tips to bear in mind when writ­ing new styles:

Use class select­ors for the major­ity of your styles #

There’s sev­er­al reas­ons to prefer class select­ors over oth­er types of selectors:

Avoid styl­ing using ID select­ors #

Declar­a­tions that use IDs select­ors have very high spe­cificity. It’s very unlikely that you need to use them. Prefer styl­ing with class select­ors instead.

Avoid !import­ant tags #

When there’s !important tags every­where, it’s a sign that spe­cificity has got­ten out of con­trol. For the vast major­ity of situ­ations, you shouldn’t need them.

The excep­tion to this is when you need to over­ride high-spe­­ci­ficity styles gen­er­ated by third party lib­rar­ies. If for some reas­on, a lib­rary adds inline styles to an ele­ment you want to style, you’re going to have to resort to an !important to over­ride it.

Avoid multi-part declar­a­tions for scop­ing styles #

Every tag you add a select­or to, the high­er it’s spe­cificity becomes.

If you write declar­a­tions that tar­get des­cend­ants (e.g. .header .title) you’ll increase it’s spe­cificity for every des­cend­ant you spe­cify. But we reg­u­larly want to write styles that are​“scoped” to a spe­cif­ic container.

Instead we could incor­por­ate the container’s class name into the element’s class name. E.g. instead of .header .title, we could change the element’s class name to .header-title and style on that instead. Using this meth­od, we scope the style by​“namespa­cing” the class name with a pre­fix and avoid increas­ing specificity.

Avoid multi-part declar­a­tions for vari­ations #

Some­times we need to change an element’s styles depend­ing on what state it’s in. For example, we may have some .button ele­ments, of which some may be dis­abled. A com­mon approach would be to add a .disabled class to the ele­ments and style them using a .button.disabled select­or. Like with the pre­vi­ous sec­tion, doing so would increase specificity.

Instead we could employ a sim­il­ar strategy and pre­fix the​“vari­ation” class with the container’s class name. Instead of .button.disabled, we’d have .button-disabled. That way the vari­ation is again scoped to the con­tain­er, and also has low specificity.

Use load order to con­trol over­rides #

Let’s return to the sub­ject of spe­cificity ties. When two or more declar­a­tions with equal spe­cificity match the same ele­ment, the one that is loaded last wins. We can use this to our advantage.

If we man­age to cre­ate a stylesheet mainly com­prised of class select­or declar­a­tions, all of which share the same spe­cificity, we can con­trol which declar­a­tions are most import­ant simply by chan­ging the order in which they appear in the stylesheet. We essen­tially side step spe­cificity and are able to employ a more intu­it­ive and man­age­able over­ride system.

As our vari­ation classes should always over­ride default styles, we can place them last in our stylesheet. Provid­ing no high­er-spe­cificity declar­a­tions have snuck in, they should always over­ride the default declarations.

End of Les­son 1 #

How spe­cificity works should hope­fully now make a little bit more sense. It’s big part of CSS, but also one that most com­monly causes prob­lems. When writ­ing new styles, always be think­ing about keep­ing spe­cificity as low as pos­sible, oth­er­wise you may end up in a bit of a mess.

Use­ful resources #