Stop breaking inheritance

Tuesday 4th November

On the correct use of inheritance.

One of the most frustrating CSS issues I’ve ever had to deal with was the maintenance of an existing stylesheet that included the rule:

html * {
    font-size: 12px;
}

Ignoring the bad style of the absolute font-size, the problem here is all about that dreaded universal selector. It seems as if this CSS is a hangover from that brief period of time during which everyone thought it was a good idea to reset spacing like so:

* { margin: 0; padding: 0; }

This simplistic reset had its own problems, but they are nothing compared to the example given originally. As a demonstration of why the above is just so bad, take the following CSS as an example:

#test2 * { font-size: 12px; }
#test2 p { font-size: 18px; }

The '#test2 *' declaration is an equivalent of the nasty 'html *' rule. The second declaration shows the author’s intent to style paragraphs in a larger font size. However, when applied to this HTML:

<div id="test2">
    <p>A sample paragraph with some
    <em>emphasised</em> text.</p>
</div>

it produces the following rendering:

A sample paragraph with some emphasised text.

Certainly not what was intended: that emphasised text is much smaller than the base paragraph text. What’s happening here is that the universal selector rule is — in effect — expanded to:

#test2 p    { font-size: 12px; }
#test2 p em { font-size: 12px; }

Whilst the explicit "#test2 p" rule takes effect due to specificity, the second rule goes on to override just the child element’s style so, in effect, our final style rules are:

#test2 p    { font-size: 18px; }
#test2 p em { font-size: 12px; }

The second rule explicitly overrides the inheritance of the containing paragraph’s font-size, that the author wished to apply. Since the universal selector will always match a parent’s child elements, it will override any inherited properties.

To clean things up (assuming the universal selector rule must remain), we need to target every element that might match, either explicitly:

#test2 *     { font-size: 12px; }
#test2 p     { font-size: 18px; }
#test2 p em  { font-size: 18px; }

or by introducing another rule containing the universal selector:

#test2 *    { font-size: 12px; }
#test2 p    { font-size: 18px; }
#test2 p *  { font-size: 18px; }

When different elements come into play, or hierarchies get deeper, this quickly becomes an unmaintainable mess.

Much like the !important problem, we just end up propogating a whole load of needless syntax to achieve what can be done much more easily. So why didn’t this problem cause the spacing reset to be vetoed immediately? Since margin and padding don’t inherit anyway, it makes little difference that inheritance is broken for this specific case. However, it takes discipline to identify those properties for which breaking inheritance is ‘safe’, and a much safer rule of thumb is simply: ignore the universal selector. I've yet to find a solid use case for it.

Update

Wednesday 4th February 2009

If you’ve arrived here from a web search, wishing to stop CSS properties from inheriting, please see this article and tell me why.


Comments

Thu 8 Apr 2010 03:14

Hristo

Hristo said:

Well, a lot of people keep writing how bad is to use the universal selector for resetting margins and padding, but no one writes why he thinks so. Give me some credible explanations to convince me!

Thu 8 Apr 2010 03:46

Five Minute Argument

Five Minute Argument said:

@Hristo: I don’t think the "* { margin: 0; padding: 0 }" rule is particularly bad, since those properties don’t inherit. However, I have a slight aversion to CSS resets in general, since they require a lot of discipline to actually override all instances correctly. IMO, the browser defaults serve a useful purpose, and I prefer to adjust them where appropriate rather than removing them altogether.

Leave a comment