CSS specificity is an important tool in the arsenal of web designers and developers and anyone who changes the design of their site by playing around with cascading style sheets. If you have ever been in the situation that you just couldn’t get an element to behave or a look the way you wanted it to, you have likely felt its power.
But, wouldn’t it be great to harness CSS specificity for your own gains rather than feel at the mercy of it? If you just nodded empathetically, then you have come to the right place. Right here, right now, we will go over this important concept so you can use it to control the look and feel of your website and WordPress theme instead of the other way around.
Understanding CSS Specificity: A CSS Crash Course
So, what exactly is it that we are talking about when we say CSS specificity? The short version is that it’s how the browser decides which property value applies to which element on the page.
To understand this process, you first need to understand how CSS works in general. For that, let’s first settle on some terminology. Here’s a typical piece of CSS markup:
.selector {
property: value;
}
What do all these things mean?
selector
— This is the part that describes the element this piece of CSS applies to. It can be somethingdiv
,p
,h1
, or a class or id like.widget
or#main-navigation
.property
— The rule applied to the selected element. Can be anything frommargin
overcolor
toflex
.value
— This is the value of the property, for example, it might be20px
for the propertymargin-left
.
How to Override CSS
In addition to the above, you need to know that browsers process style sheets from top to bottom. That means declarations that appear later in the style sheet overwrite those that came before.
.widget {
font-size: 18px;
}
.widget {
font-size: 16px;
}
In the example above, you can see that both declarations target the same selector and property. However, because the latter is at the bottom, its value will prevail. The browser will always use the last declaration.
However, there is an exception. If the first declaration is more specific than the one following it, it will continue to apply instead. For example, with the markup below, any element with the class widget
will have their font size set to 18 pixels instead 16.
.sidebar .widget {
font-size: 18px;
}
.widget {
font-size: 16px;
}
That’s because .sidebar .widget
is more targeted than just .widget
. And that’s pretty much the gist of CSS specificity.
Why Does This Matter?
So, why is knowing CSS specificity important? Because it is the principle that determines which properties and values apply to a particular element. This includes cases where there are potential conflicts.
As a consequence, CSS specificity is the solution to many cases where you are having problems getting styles to show up on page. It often ends up being a problem of specificity.
Conversely, if you are a theme author, knowing how to properly use specificity is crucial in order to not frustrate users who want to make adjustments, e.g. in a child theme. For that purpose, we will go over some best practices further below.
So, How Is Specificity Calculated?
In order to be able to troubleshoot problems of specificity, you need to be aware of how it works. There are clear rules governing it, and when you understand them, you can use them.
The Order of Selectors
First of all, selectors all have different weights in terms of specificity. Here they are in ascending order, from less to more specific:
- Type selectors — Think
div
,h1
,a
,p
but also pseudo-elements like:before
and:after
. - Class selectors — that means normal classes like
.site-header
, attribute selectors likep[class=footer-credit]
but also pseudo-classes such as:hover
and:focus
. - ID selectors — These are selectors that usually only uniquely apply to one specific element per page, written like
#main-navigation
. - Inline styles — Inline declarations like
<p style="color:#999;">
always overwrite styling in external CSS files. The same is true for styles declared in the<style>
section directly in the HTML file. They, too, take precedence.
Here’s an example to drive the point home:
#optin-form {
color: red;
}
[id="optin-form"] {
color: blue;
}
Even though both selectors technically target the id, one of them is an attribute selector while the other is an id selector. As a consequence, the latter takes the cake.
Specificity Values
“Calculated” is also not a misnomer in the case of CSS specificity. Browsers indeed apply numerical values to different types of selectors to figure out their specificity. It starts at 0 (0,0,0,0), type selectors have a value of 1 (0,0,0,1), class selectors score 10 (0,0,1,0), ids 100 (0,1,0,0), and inline styles 1,000 (1,0,0,0).
You also add them up to each other. So, if you are using an id followed by a type selector (like #main-navigation a
) it has a a value of 101.
However, it’s important to keep in mind that this is not actually a base-10 system. For example, (0,2,11,3) is not the same as (0,3,1,3). Yet, for simplicity’s sake, it’s ok to think about it in these terms.
Selectors with their specificity set to 0 include things like the universal selector *
, combinators such as >
, +
, ~
, inherited values, and also media queries. That means they also don’t increase specificity. More on that below.
Let’s Get More Specific: Rules and Examples
First of all, apologies for the pun above. Secondly, now that you know how CSS specificity works in general, there are, of course, more things to know about it.
General Rules
First of all, the general CSS rules still apply. When you have multiple declarations that are equally specific and targeting the same element, the declaration that appears last in the style sheet applies.
.sidebar .widget {
font-style: normal;
}
.container .widget {
font-style: italic;
}
Because they are each made up of two class selectors, both of these declarations have the same level of specificity. As a consequence, the rule of the cascade applies and any text inside the widget ends up italic.
On the other hand, proximity does not matter. If you look at the accompanying HTML markup below, you can see that sidebar
is closer to the target of the CSS than container
.
<div class="container">
<div class="sidebar">
<div class="widget">
<p id="text-example">I am an example.</p>
</div>
</div>
</div>
However, just because it’s closer, that doesn’t mean it gets rendered. With the same selector type count, the value of the one that is declared last prevails.
CSS Specificity and Inheritance
Specificity is also important in the context of inheriting values. Certain properties, such as font-family
or color
, when set to a parent element, also automatically apply to its children. This is called inheritance and it’s why you can set typography for an entire site by applying it to the body
tag.
However, it’s important to note how specificity works in this context. It turns out, when your target elements directly, that will always take precedence over inherited markup, regardless of how specific the inherited rule is. In the context of the example above, any property applied to widget
will win out against those inherited from container
.
.container {
font-family: Tahoma;
}
.widget {
font-family: Verdana;
}
Specificity and !important
There is an elephant in the room when it comes to CSS specificity and its name is !important
. If you are not familiar with this property, it’s a way to cut through specificity. You can add !important
at the end of any property value to make it overwrite any values that come after.
p {
background-color: yellow !important;
}
.container .sidebar .widget #text-example{
background-color: lightgreen;
}
In the example above, the second declaration is clearly way more specific than the first. However, just because the first one includes !important
, the background color ends up yellow, not light green.
Though technically not related to CSS specificity, the !important
rule clearly has an impact on it. We will talk about the proper use of the !important
rule further below. For now, it matters that you note that specificity applies here as well.
When you have two conflicting declarations both using !important
, the one with greater specificity will win out. On the other hand, if you have two !important
statements with the same level of specificity, the cascade applies and the last statement is the one the browser will use.
:is(), :not(), and :where()
There are some interesting cases with some CSS functions. Two examples are :is()
and :not()
. These are technically pseudo-classes but aren’t considered as such in specificity. Yet, any selectors placed inside them count usual.
:is(.container .widget) p{
color: darkgoldenrod;
}
.container p {
color: blanchedalmond;
}
In this case, :is()
doesn’t count towards specificity, however, its contents .container .widget
do and theirs is higher than just .container
below. Hence, the text will be dargoldenrod and not blanchedalmond colored.
(It should also become obvious that I am taking a deep dive into what color names CSS has to offer.)
:where()
on the other hand is a very new CSS feature that, as mentioned in the linked article, always has a specificity of zero. So, if you use it in the above example in place of :is()
, the outcome will be exactly the opposite.
CSS Specificity Best Practices
Now that we know the details of how it works, let’s go over some best practices for using CSS specificity.
Writing Markup
The first thing you need to know is how to use this when writing CSS. That means, how you can use CSS specificity in a way that achieves your results, produces clean code, and also allows others to make adjustments to your design without ripping their hair out or reverting to the !important
nuclear option.
The basic guiding principle here is to use the least number of selectors necessary. As mentioned, style sheets cascade down, so you can use generic selectors for the broad strokes and then get more specific when necessary.
Open the style sheet of any modern WordPress theme and you will already see this principle at work:
body {
color: #111;
font-family: "NonBreakingSpaceOverride", "Hoefler Text", Garamond, "Times New Roman", serif;
font-weight: 400;
font-size: 1em;
}
.main-navigation .main-menu > li > a {
color: #0073aa;
font-weight: 700;
}
The markup above is from the Twenty Nineteen theme. As you can see, it defines standards for all typography via the body
selector and then overwrites the color and font weight with a more specific declaration where necessary.
A second principle is to rely more on specificity than the order of selectors, otherwise you might end up constantly shuffling around bits of code so that your selectors are in the right place in order to override each other. This makes style sheets very hard to maintain.
Finally, you might want to look into CSS naming architecture like BEM that have thought about naming conventions for specificity.
How to Override Existing CSS
When trying to make changes to an existing theme or working with a child theme, you will often run into having to create more specific declarations. This is actually simple enough, you can often achieve it by including one or more elements before your target. This automatically increases the level of specificity.
Here, too, the same applies as above. Only use just as much as it takes to achieve your goal. In the example we have been using, you can target the text simply via .container p
or #example-text
. You could also do .container .sidebar .widget #text-example
, however, it wouldn’t improve anything and also make your CSS harder to read and, if necessary, overwrite further down.
When to Use !important
Last but not least, we need to talk about !important
. As already mentioned, you should use this as sparingly as possible. Achieving your goals this property is the equivalent of a bulldozer and can be hell to debug (as you might have felt at some point in your career, trying to overwrite someone else’s markup). Therefore, you should always try to figure out the existing CSS structure and how to use specificity to overwrite it instead.
However, there are a few cases in which using !important
is legitimate:
- For debugging purposes, e.g. to see if your intended CSS can apply and what it would look like.
- Overriding inline styles that you have no other way of changing.
- When you want to determine that an element should use specific styling no matter what.
- To overwrite specific CSS that comes from an external library. E.g. when using Bootstrap, you might encounter some things that you can’t override with specificity.
- To override existing declarations with
!important
that you have no way of changing (e.g. that are part of a plugin). In this case, pay attention to the specificity rules we talked about above.
CSS Specificity Tools
In the final part, let’s go over some tools for learning/dealing with specificity:
- CSS specificity calculator — This is a calculator to figure out the specificity of selectors. If you are stuck, simply copy and paste them in there. The site will give you a numeric value, which helps you figure out why something is not working.
- Specifishity — A fun diagram that explains specificity visually using fish.
- CSS Specificity Wars — Similar to Specifishity but with Star Wars figurines.
Summing Up…
CSS specificity issues are one of those things that you will encounter one way or another when meddling with style sheets, design, and markup. They can be frustrating at first, when you absolutely can’t figure out “why this damn thing won’t do what I tell it to!”. However, once you understand it, CSS specificity is a very useful tool to have in your web design utility belt.
Above, we have gone over the basic principles of how it works, examples, best practices, and some tools. You should now be able to use it in the wild, which will hopefully save you some nerves in the future.
Do you have a story where CSS specificity got the best of you? Please do tell in the comments!
No Comments