Every now and then, I stumble onto an old project of mine, or worse, someone else’s, and I’m reminded just how chaotic CSS can get over time. In most of these cases, the !important keyword seems to be involved in one way or another. And it’s easy to understand why developers rely on it. It provides an immediate fix and forces a rule to take precedence in the cascade.
That’s not to say !important doesn’t have its place. The problem is that once you start using it, you’re no longer working with the cascade; you’re bypassing it. This can quickly get out of hand in larger projects with multiple people working on them, where each new override makes the next one harder.
Cascade layers, specificity tricks, smarter ordering, and even some clever selector hacks can often replace !important with something cleaner, more predictable, and far less embarrassing to explain to your future self.
Let’s talk about those alternatives.
Specificity and !important
Selector specificity is a deep rabbit hole, and not the goal of this discussion. That said, to understand why !important exists, we need to look at how CSS decides which rules apply in the first place. I wrote a brief overview on specificity that serves as a good starting point. Chris also has a concise piece on it. And if you really want to go deep into all the edge cases, Frontend Masters has a thorough breakdown.
In short, CSS gives each selector a kind of “weight.” When two rules target the same element, the rule with higher specificity wins. If the specificity is equal, the rule declared later in the stylesheet takes precedence.
- Inline styles (
style="...") are the heaviest. - ID selectors (
#header) are stronger than classes or type selectors. - Class, attribute, and pseudo-class selectors (
.btn,[type="text"],:hover) carry medium weight. - Type selectors and pseudo-elements (
div,p,::before) have the lowest weight. Although, the*selector is even lower with a specificity of 0-0-0 compared to type selectors which have a specificity of 0-0-1.
/* Low specificity (0,0,1) */
p {
color: gray;
}
/* Medium specificity (0,1,0) */
.button {
color: blue;
}
/* High specificity (1,1,0) */
#header .button {
color: red;
}
<!-- Inline style (1,0,0) -->
<p style="color: green;">Hello</p>
Inline styles being the heaviest also explains why they’re often frowned upon and not considered “clean” CSS since they bypass most of the normal structure we try to maintain.
!important changes this behavior. It skips normal specificity and source order, pushing that declaration to the top within its origin and cascade layer:
p {
color: red !important;
}
#main p {
color: blue;
}
Even though #main p is more specific, the paragraph will appear red because the !important declaration overrides it.
Why !important can be problematic
Here’s the typical lifecycle of !important in a project involving multiple developers:
“Why isn’t this working? Add !important. Okay, fixed.”
Then someone else comes along and tries to change that same component. Their rule doesn’t apply, and after some digging, they find the !important. Now they have a choice:
- remove it and risk breaking something else,
- or add another
!importantto override it.
And since no one is completely sure why the first one was added, the safer move often feels like adding another one. This can quickly spiral out of control in larger projects.
On a more technical note, the fundamental problem with !important is that it breaks the intended order of the cascade. CSS is designed to resolve conflicts predictably through specificity and source order. Later rules override earlier ones, and more specific selectors override less specific ones.
A common place where this becomes obvious is theme switching. Consider the example below:
.button {
color: red !important;
}
.dark .button {
color: white;
}
Even inside a dark theme, the button stays red. This results in the stylesheet becoming harder to reason about, because the cascade is no longer predictable.
In large teams, especially, this results in maintenance and debugging becoming harder. None of this means !important should never be used. There are legitimate cases for it, especially in utility classes, accessibility overrides, or user stylesheets. But if you’re using it as your go-to method to resolve a selector/styling conflict, it’s usually a sign that something else in the cascade needs attention.
Let’s look at alternatives.
Cascade layers
Cascade layers are a more advanced feature of CSS, and there’s a lot of theory on them. For the purposes of this discussion, we’ll focus on how they help you avoid !important. If you want to learn more, Miriam Suzanne wrote a complete guide on CSS Cascade Layers on it that goes into considerable detail.
In short, cascade layers let you define explicit priority groups in your CSS. Instead of relying on selector specificity, you decide up front which category of styles should take precedence. You can define your layer order up front:
@layer reset, defaults, components, utilities;
This establishes priority from lowest to highest. Now you can add styles into those layers:
@layer defaults {
a:any-link {
color: maroon;
}
}
@layer utilities {
[data-color='brand'] {
color: green;
}
}
Even though [data-color='brand'] has lower specificity than a:any-link, the utilities layer takes precedence because it was defined later in the layer stack.
It’s worth noting that specificity still works inside a layer. But between layers, layer order is given priority.
With cascade layers, you can prioritize entire categories of styles instead of individual rules. For example, your “overrides” layer always takes precedence over your “base” layer. This sort of architectural thinking, instead of reactive fixing saves a lot of headaches down the line.
One very common example is integrating third-party CSS. If a framework ships with highly specific selectors, you can do this:
@layer framework, components;
@import url('framework.css') layer(framework);
@layer components {
.card {
padding: 2rem;
}
}
Now your component styles automatically override the framework styles, regardless of their selector specificity, as long as the framework isn’t using !important.
And while we’re talking about it, it’s good to note that using !important with cascade layers is actually counterintuitive. That’s because !important actually reverses the layer order. It is no longer a quick way to jump to the top of the priorities — but an integrated part of our cascade layering; a way for lower layers to insist that some of their styles are essential.
So, if we were to order a set of layers like this:
utilities(most powerful)componentsdefaults(least powerful)
Using !important flips things on their head:
!important defaults(most powerful)!important components!important utilities- normal
utilities - normal
components - normal
defaults(least powerful)
Notice what happens there: it generates three new, reversed important layers that supersede the original three layers while reversing the entire order.
The :is() pseudo
The :is() pseudo-class is interesting because it takes the specificity of its most specific argument. Say you have a component that needs to match the weight of a more specific selector elsewhere in the codebase:
/* somewhere in your styles */
#sidebar a {
color: gray;
}
/* your component */
.nav-link {
color: blue;
}
Rather than using !important, you can bump .nav-link up by wrapping it in :is() with a more specific argument:
:is(#some_id, .nav-link) {
color: blue;
}
Now this has id-level specificity while matching only .nav-link. It’s worth noting that the selector inside :is() doesn’t have to match an actual element. We’re using #some_id purely to increase specificity in this case.
Note: If #some_id actually exists in your markup, this selector would also match that element. So it would be best to use an id not being used to avoid side effects.
On the flip side, :where() does the opposite. It always resolves to a specificity of (0,0,0), no matter what’s inside it. This is handy for reset or base styles where you want anything downstream to override easily.
Doubling up a selector
A pretty straightforward way of increasing a selectors specificity is repeating the selector. This is usually done with classes. For example:
.button {
color: blue;
}
.button.button {
color: red; /* higher specificity */
}
You would generally not want to do this too often as it can become a readability nightmare.
Reordering
CSS resolves ties in specificity by source order, so a rule that comes later is prioritized. This is easy to overlook, especially in larger stylesheets where styles are spread across multiple files.
If a more generic rule keeps overriding a more targeted one and the specificity is the same, check whether the generic rule is being loaded after yours. Flipping the order can fix the conflict without needing to increase specificity.
This is also why it’s worth thinking about stylesheet organization from the start. A common pattern is to go from generic to specific (resets and base styles first, then layout, then components, then utilities).
When using !important does make sense
After all that, it’s worth being clear: !important does have legitimate use cases. Chris discussed this a while back too, and the comments are worth a read too.
The most common case is utility classes. For example, the whole point of classes like .visually-hidden is that they do one thing, everywhere. In this cases, you don’t want a more specific selector quietly undoing it somewhere else. The same is true for state classes like .disabled or generic component styles like .button.
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
clip-path: inset(50%) !important;
}
Third-party overrides are another common scenario. !important can be used here to either override inline styles being set in JavaScript or normal styles in a stylesheet that you can’t edit.
From an accessibility point of view, !important is irreplaceable for user stylesheets. Since these are applied on all webpages and there’s virtually no way to guarantee if the stylesheets’ selectors will always have the highest specificity, !important is basically the only reliable way to make sure your styles always get precedence.
Another good example is when it comes to respecting a user’s browser preferences, such as reducing motion:
@media screen and (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
Wrapping up
The difference between good and bad use of !important really comes down to intent. Are you using it because you understand the CSS Cascade and have made a call that this declaration should always apply? Or are you using it as a band-aid? The latter will inevitably cause issues down the line.
Further reading
- Specifics on CSS Specificity by Chris Coyier
- Tackling CSS Specificity by Emma Bostian
- Cascade Layers Guide by Miriam Suzanne
- When Using !important is The Right Choice by Chris Coyier
Alternatives to the !important Keyword originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
from CSS-Tricks https://ift.tt/KAVyCua
via IFTTT






