Now and again, I stumble onto an outdated challenge of mine, or worse, another personβs, and Iβm reminded simply how chaotic CSS can recover from time. In most of those instances, the !vital key phrase appears to be concerned in a technique or one other. And itβs simple to grasp why builders depend on it. It supplies a direct repair and forces a rule to take priority within the cascade.
Thatβs to not say !vital doesnβt have its place. The issue is that when you begin utilizing it, youβre not working with the cascade; youβre bypassing it. This will shortly get out of hand in bigger initiatives with a number of individuals engaged on them, the place every new override makes the following one tougher.
Cascade layers, specificity methods, smarter ordering, and even some intelligent selector hacks can typically exchange !vital with one thing cleaner, extra predictable, and much much less embarrassing to elucidate to your future self.
Letβs discuss these alternate options.
Specificity and !vital
Selector specificity is a deep rabbit gap, and never the aim of this dialogue. That stated, to grasp why !vital exists, we have to have a look at how CSS decides which guidelines apply within the first place. I wrote a temporary overview on specificity that serves as an excellent place to begin. Chris additionally has a concise piece on it. And if you happen to actually need to go deep into all the sting instances, Frontend Masters has a thorough breakdown.
In brief, CSS provides every selector a sort of βweight.β When two guidelines goal the identical component, the rule with larger specificity wins. If the specificity is equal, the rule declared later within the stylesheet takes priority.
- Inline types (
model="...") are the heaviest. - ID selectors (
#header) are stronger than lessons or kind selectors. - Class, attribute, and pseudo-class selectors (
.btn,[type="text"],:hover) carry medium weight. - Sort selectors and pseudo-elements (
div,p,::earlier than) have the bottom weight. Though, the*selector is even decrease with a specificity of 0-0-0 in comparison with kind selectors which have a specificity of 0-0-1.
/* Low specificity (0,0,1) */
p {
coloration: grey;
}
/* Medium specificity (0,1,0) */
.button {
coloration: blue;
}
/* Excessive specificity (1,1,0) */
#header .button {
coloration: pink;
}
Howdy
Inline types being the heaviest additionally explains why theyβre typically frowned upon and never thought-about βclearβ CSS since they bypass many of the regular construction we attempt to keep.
!vital adjustments this habits. It skips regular specificity and supply order, pushing that declaration to the highest inside its origin and cascade layer:
p {
coloration: pink !vital;
}
#major p {
coloration: blue;
}
Although #major p is extra particular, the paragraph will seem pink as a result of the !vital declaration overrides it.
Why !vital may be problematic
Right hereβs the everyday lifecycle of !vital in a challenge involving a number of builders:
βWhy isnβt this working? Add !vital. Okay, fastened.β
Then another person comes alongside and tries to vary that very same part. Their rule doesnβt apply, and after some digging, they discover the !vital. Now they’ve a selection:
- take away it and danger breaking one thing else,
- or add one other
!vitalto override it.
And since nobody is totally certain why the primary one was added, the safer transfer typically appears like including one other one. This will shortly spiral uncontrolled in bigger initiatives.
On a extra technical observe, the elemental downside with !vital is that it breaks the meant order of the cascade. CSS is designed to resolve conflicts predictably by means of specificity and supply order. Later guidelines override earlier ones, and extra particular selectors override much less particular ones.
A typical place the place this turns into apparent is theme switching. Contemplate the instance under:
.button {
coloration: pink !vital;
}
.darkish .button {
coloration: white;
}
Even inside a darkish theme, the button stays pink. This leads to the stylesheet turning into tougher to cause about, as a result of the cascade is not predictable.
In massive groups, particularly, this leads to upkeep and debugging turning into tougher. None of this implies !vital ought to by no means be used. There are respectable instances for it, particularly in utility lessons, accessibility overrides, or consumer stylesheets. However if you happen toβre utilizing it as your go-to technique to resolve a selector/styling battle, itβs often an indication that one thing else within the cascade wants consideration.
Letβs have a look at alternate options.
Cascade layers
Cascade layers are a extra superior characteristic of CSS, and thereβs a lot of principle on them. For the needs of this dialogue, weβll give attention to how they allow you to keep away from !vital. If you wish to study extra, Miriam Suzanne wrote a full information on CSS Cascade Layers on it that goes into appreciable element.
In brief, cascade layers allow you to outline specific precedence teams in your CSS. As a substitute of counting on selector specificity, you determine up entrance which class of types ought to take priority. You possibly can outline your layer order up entrance:
@layer reset, defaults, elements, utilities;
This establishes precedence from lowest to highest. Now you’ll be able to add types into these layers:
@layer defaults {
a:any-link {
coloration: maroon;
}
}
@layer utilities {
[data-color="brand"] {
coloration: inexperienced;
}
}
Although [data-color="brand"] has decrease specificity than a:any-link, the utilities layer takes priority as a result of it was outlined later within the layer stack.
Itβs value noting that specificity nonetheless works inside a layer. However between layers, layer order is given precedence.
With cascade layers, you’ll be able to prioritize total classes of types as an alternative of particular person guidelines. For instance, your βoverridesβ layer at all times takes priority over your βbaseβ layer. This kind of architectural considering, as an alternative of reactive fixing saves quite a lot of complications down the road.
One quite common instance is integrating third-party CSS. If a framework ships with extremely particular selectors, you are able to do this:
@layer framework, elements;
@import url('framework.css') layer(framework);
@layer elements {
.card {
padding: 2rem;
}
}
Now your part types robotically override the framework types, no matter their selector specificity, so long as the framework isnβt utilizing !vital.
And whereas weβre speaking about it, itβs good to notice that utilizing !vital with cascade layers is definitely counterintuitive. Thatβs as a result of !vital really reverses the layer order. It’s not a fast approach to soar to the highest of the priorities β however an built-in a part of our cascade layering; a means for decrease layers to insist that a few of their types are important.
So, if we have been to order a set of layers like this:
utilities(strongest)elementsdefaults(least highly effective)
Utilizing !vital flips issues on their head:
!vital defaults(strongest)!vital elements!vital utilities- regular
utilities - regular
elements - regular
defaults(least highly effective)
Discover what occurs there: it generates three new, reversed vital layers that supersede the unique three layers whereas reversing your complete order.
The :is() pseudo
The :is() pseudo-class is attention-grabbing as a result of it takes the specificity of its most particular argument. Say you may have a part that should match the burden of a extra particular selector elsewhere within the codebase:
/* someplace in your types */
#sidebar a {
coloration: grey;
}
/* your part */
.nav-link {
coloration: blue;
}
Moderately than utilizing !vital, you’ll be able to bump .nav-link up by wrapping it in :is() with a extra particular argument:
:is(#some_id, .nav-link) {
coloration: blue;
}
Now this has id-level specificity whereas matching solely .nav-link. Itβs value noting that the selector inside :is() doesnβt need to match an precise component. Weβre utilizing #some_id purely to extend specificity on this case.
Be aware: If #some_id really exists in your markup, this selector would additionally match that component. So it will be greatest to make use of an id not getting used to keep away from negative effects.
On the flip facet, :the place() does the other. It at all times resolves to a specificity of (0,0,0), it doesn’t matter whatβs inside it. That is useful for reset or base types the place you need something downstream to override simply.
Doubling up a selector
A fairly simple means of accelerating a selectors specificity is repeating the selector. That is often accomplished with lessons. For instance:
.button {
coloration: blue;
}
.button.button {
coloration: pink; /* larger specificity */
}
You’d typically not need to do that too typically as it could develop into a readability nightmare.
Reordering
CSS resolves ties in specificity by supply order, so a rule that comes later is prioritized. That is simple to miss, particularly in bigger stylesheets the place types are unfold throughout a number of recordsdata.
If a extra generic rule retains overriding a extra focused one and the specificity is similar, examine whether or not the generic rule is being loaded after yours. Flipping the order can repair the battle without having to extend specificity.
That is additionally why itβs value fascinated about stylesheet group from the beginning. A typical sample is to go from generic to particular (resets and base types first, then structure, then elements, then utilities).
When utilizing !vital does make sense
In spite of everything that, itβs value being clear: !vital does have respectable use instances. Chris mentioned this some time again too, and the feedback are value a learn too.
The commonest case is utility lessons. For instance, the entire level of lessons like .visually-hidden is that they do one factor, all over the place. On this instances, you donβt need a extra particular selector quietly undoing it some other place. The identical is true for state lessons like .disabled or generic part types like .button.
.visually-hidden {
place: absolute !vital;
width: 1px !vital;
top: 1px !vital;
overflow: hidden !vital;
clip-path: inset(50%) !vital;
}
Third-party overrides are one other widespread state of affairs. !vital can be utilized right here to both override inline types being set in JavaScript or regular types in a stylesheet that you couldβt edit.
From an accessibility perspective, !vital is irreplaceable for consumer stylesheets. Since these are utilized on all webpages and thereβs just about no approach to assure if the stylesheetsβ selectors will at all times have the best specificity, !vital is principally the one dependable means to verify your types at all times get priority.
One other good instance is in relation to respecting a consumerβs browser preferences, comparable to lowering movement:
@media display screen and (prefers-reduced-motion: cut back) {
* {
animation-duration: 0.001ms !vital;
animation-iteration-count: 1 !vital;
transition-duration: 0.001ms !vital;
}
}
Wrapping up
The distinction between good and dangerous use of !vital actually comes all the way down to intent. Are you utilizing it since you perceive the CSS Cascade and have made a name that this declaration ought to at all times apply? Or are you utilizing it as a band-aid? The latter will inevitably trigger points down the road.









