Richard Hamilton
Richard Hamilton

Reputation: 26444

CSS Specificity is not following the formula

I have the following code

<style type="text/css">
    .el3 { font-family: Arial, sans-serif; }
    .el1, .el3 { font-family: Tahoma, sans-serif; }
    .el1, .el2, .el3 { font: Helvetica; }
</style>

<div class="el3">Text</div>

When I load up my browser and inspect the element with class el3 I find that the font-family is Tahoma, sans-serif.

I know that I didn't add the classes .el1 and .el2 to my HTML, but I still expected the result to be Helvetica. When I inspect the element, that ruling is crossed out.

There are two reasons why the font should be Helvetica.

The specificity formula is as follows

  1. Add 1 point if the style is inline
  2. Add 1 point for every id selector in the query
  3. Add 1 point for every class, attribute or pseudo class selector
  4. Add 1 point for every element or pseudo element

The score is typically measured in bracket notation. In my example, the three rules would be given these three specificity scores

.el1                    => {0,0,1,0}
.el1,.el2               => {0,0,2,0}
.el1, .el2, .el3        => {0,0,3,0}

It shouldn't make a difference whether .el1 and .el2 are defined. 3 class selectors should have higher precedence than 2 class selectors.

The second reason is that even if they both had the same precedence, the tiebreaker would go to rule 3 because it appears last.

It also can't possibly be because for the first two rules I used the font-family property and for the last one I used font.

Based on the precedence rules, the font should clearly be Helvetica.

Upvotes: 1

Views: 270

Answers (1)

Anthony
Anthony

Reputation: 37065

The issue isn't specificity, it's validity. With the given rules:

.el3 { font-family: Arial, sans-serif; }
.el1, .el3 { font-family: Tahoma, sans-serif; }
.el1, .el2, .el3 { font: Helvetica; }

The last one isn't applied because it's treated as invalid. You can confirm by testing:

.el3 { font: Arial, sans-serif; }
.el1, .el3 { font: Tahoma, sans-serif; }
.el1, .el2, .el3 { font: Helvetica; }

Where none of the font-families are applied. This seems to be related to how shorthand properties (like font) are validated. If I change the second set of test rules to:

.el3 { font: 1em Arial, sans-serif; }
.el1, .el3 { font: 1em Tahoma, sans-serif; }
.el1, .el2, .el3 { font: 1em Helvetica; }

Then the last rule gets applied. This seems contrary to the CSS spec on shorthand properties which actually warn that using a shorthand without specifying all properties will result in the property being treated as set to the initial value for the property:

When values are omitted from a shorthand form, unless otherwise defined, each “missing” sub-property is assigned its initial value.

This means that a shorthand property declaration always sets all of its sub-properties, even those that are not explicitly set. Carelessly used, this might result in inadvertently resetting some sub-properties. Carefully used, a shorthand can guarantee a “blank slate” by resetting sub-properties inadvertently cascaded from other sources. For example, writing background: green rather than background-color: green ensures that the background color overrides any earlier declarations that might have set the background to an image with background-image.

But I think since the rule isn't being applied at all, the client (browser) is instead treating the shorthand property with only one single-property being set as invalid outright.

Further Findings:

I'm not sure what their source is, but the MDN doc on the font property does mention this issue explicitly:

Note: There are a few caveats when using the CSS font shorthand. If these conditions are not met, the property is invalid and is entirely ignored.

  • Except when using a keyword, it is mandatory to define the value of both the font-size and the font-family properties.
  • Not all values of font-variant are allowed. Only the values defined in CSS 2.1 are allowed, that is normal and small-caps.
  • Though not directly settable by font, the values of font-stretch, font-size-adjust and font-kerning are also reset to their initial values.
  • The order of the values is not completely free: font-style, font-variant and font-weight must be defined, if any, before the font-size value. The line-height value must be defined immediately after the font-size, preceded by a mandatory /. Finally the font-family is mandatory and must be the last value defined (inherit value does not work).

Source from Spec:

If you look at the font property specification, it has the following:

Value: [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar | inherit

which for anyone who can sight-read EBNF (not me) can see means that the shorthand requires either (a keyword) or (font-size and font-family) to be set in order be considered valid. The grammar above looks kinda like:

[ 
    [ <'font-style'> || <'font-variant'> || <'font-weight'> ] 
    ? 
    <'font-size'> [ / <'line-height'> ] 
    ? 
    <'font-family'> 
] 
| caption | icon | menu | message-box | small-caption | status-bar | inherit

Upvotes: 3

Related Questions