Css asterisk selector specificity
Today I stumbled upon a tricky little challenge while designing a stack component in a design system. A stack is considered a layout component, primarily positioning boxes beneath each other (very much as html block elements would behave by default). The stack however defines the horizontal margins between each child component, to keep vertical rhythm and spacings consistent across the page.
Usually how we would solve this is a css selector that resets the margins of its child elements and overrides it, like this:
.stack > * { margin-top: 0; margin-bottom: 0; }
.stack > * + * { margin-top: 1.5rem; }
My understanding was, that the combined selector of .stack > * + *
has a
higher specificity as another class selector, which would override the styling
of whatever element / component was put into the stack as child. I was quite
baffled, when a headline component had the following styling, which beat the
stack selector.
.heading {
margin: 0;
}
The heading within the stack applied the margin: 0
from the .heading
selector and overriding the stacks styles. With the help of my colleague
@ddprrt we were able to find out why. Turns
out the *
selector in css, does not have a specificity.
Universal selector (*), combinators (+, >, ~, ' ', ||) and negation pseudo-class (:not()) have no effect on specificity. (The selectors declared inside :not() do, however.) MDN on specificity
I was happy, that after so many years of learning and writing css, I was still able to learn something new.
# Solutions
As solutions, there are a couple of approaches that can be taken, depending on how hard you want to enforce the margin rules.
!important
can of course be used to enforce the stacks rule with the almighty power, but this is not always what you want to do.- Doubling up the stack selector, to increase the specificity ever so slightly.
I personally do like this approach, as it does not require a change in the
html markup. A selector like
.stack.stack
has an elevated specificity, but you are still able to overwrite the styling if you are ever in the position that you need to. - Combining
*:empty, *:not(:empty)
: Thenot
notation does not increase the specificity, but the:empty
does. By adding both variations, you end up with a similar result as in 2.