Toms journey in web development

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.

  1. !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.
  2. 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.
  3. Combining *:empty, *:not(:empty): The not notation does not increase the specificity, but the :empty does. By adding both variations, you end up with a similar result as in 2.

Back to the postlist