I think this is good advice from Silvestar Bistrović:
An enabling selector is what I call a selector that does a job without disabling the particular rule.
The classic example is applying margin
to everything, only to have to remove it from the final element because it adds space in a place you don’t want.
.card {
margin-bottom: 1rem;
}
/* Wait but not on the last one!! */
.parent-of-cards :last-child {
margin-bottom: 0;
}
You might also do…
/* "Disabling" rule */
.card:last-child {
margin-bottom: 0;
}
But that’s maybe not as contextual as selecting from the parent.
Another variation is:
.card:not(:last-child) {
margin-bottom: 1rem;
}
That’s what Silvestar refers to as “enabling” because you’re only ever applying this rule — not applying it and then removing it with another selector later. I agree that’s harder to understand and error-prone.
Yet another example is a scoped version of Lobotomized Owls:
/* Only space them out if they stack */
.card + .card {
margin-top: 1rem;
}
I think gap
is where this is all headed in the long term. Put the onus on the parent, not the child, and keep it an enabling selector:
.parent-of-cards {
display: grid;
gap: 1rem;
}
I personally love to use this selector
* + .card
. The beauty of the asterisk is that it does not increase specificity.I agree. I have seen this type of selector through my code reviews and i dismissed because of the specificity problem
Although I like the idea, of enabling vs disabling I couldn’t disagree more with how it’s implemented.
My personal preference is readability and the code that disables is explicit, it does exactly what it says. The other one takes some thoughts to understand. Now imagine combining some of them or having to maintain the code of another developer.
Especially with :not, extending the selector becomes quite frustrating but in reality you need to do it. Looking at a selector to target everything but first and last doesn’t make me happy.
Nice one there. Here’s another enabler:
.parent-of-cards > * + * {margin-top: 1rem}