XING Devblog

CSS selector specificity and complexity in development

| Posted by

Introduction to CSS selectors

Binding styles to elements of a web page

Every CSS rule consists of simple or combined selectors, followed by a declaration of style properties. A browser constructs a tree out of the HTML elements, matches CSS selectors against them, applies the styles, and finally renders the document to the screen.

#footer .vcard img { border: 1px solid green }

Full explanation: The combined selector matches any <img> element that is a descendant of any element with a class attribute that contains the word “vcard” that is a descendant of any element with an ID (unique identifier) attribute that equals “footer”.

This HTML fragment above is one example out of many of a match. After all, the selector doesn’t specify the nesting depth and what types of elements have the ID and the class attributes. Anyway, the browser must apply a green border to the image.

The specificity of a selector

If several declarations apply for a given element/property combination, a browser has to find one winning declaration.

#footer .vcard img { border: 1px solid green }
 
img { border: 1px dotted red; height: 100px }

The following cascade of criteria applies:

  1. the declaration’s origin stylesheet and the declaration’s marked importance account for its weight (browser, engineer, and user stylesheets interact)
  2. then the specificity of its selector,
  3. finally its order of appearance (the last declaration wins).

A selector’s specificity is calculated as follows:

  • count ID selectors (= a)
  • count class selectors, attribute selectors, and pseudo-classes (= b)
  • count type selectors and pseudo-elements (= c)

Concatenating the three numbers a, b, c gives the specificity of the selector.

A single ID overrules a thousand classes: Unlike the decimal system, there is no carry over, so 1,0,0 does not equal to 0,10,0 and is still more specific than 0,11,0.

#footer .vcard img { border: 1px solid green }
 
/*
  one ID, one class, and one type
  a = 1, b = 1, c = 1
*/
 
img { border: 1px dotted red; height: 100px }
 
/*
  one type
  a = 0, b = 0, c = 1
*/

The result is a solid green border (not a red dotted one) and a height of 100 px for this image. With respect to the border property, the specificity wins over the order of appearance. It is important to note that the height property of the second rule applies because there is no competing declaration.

The cascade is about finding the winning declaration for an element/property combination. Stylesheets, because of their cascading nature, show interdependencies where many rulesets add properties to the resulting style of an element.

Snowballing complexity and development costs

Elaborate selectors, e.g. those with manifold combinations of types with classes and ID’s, have drawbacks:

  • they are difficult to understand and to maintain
  • they match special HTML fragments only, which decreases the reuse of code patterns
  • they lead to subsequent selectors of same or even higher specificity to prevent overwriting

Complex selectors often reflect complex requirements: If the image of the virtual card in the footer is required to look more prominent on the logged-in profile section only, but only if this vCard is hovered and only if … (insert more requirements here), this could result in a monstrous selector like

body.profile#loggedin #footer .vcard:hover img[alt="user"]{ border: 2px solid pink}
 
/*
  two IDs, two classes and one pseudo-class and one attribute, and two type selectors
  a = 2, b = 4, c = 2
*/

This selector is probably already beyond refactoring. Nonetheless, the corresponding HTML fragment may have to be altered because of changing product requirements. Engineers will start a CSS microsurgery then, hoping that nothing will break without notice in other areas of the application. Alternatively, chunks of the CSS are rewritten from scratch. In a large project with thousands of rulesets, any change in the HTML is hardly ever without adverse effects in the CSS.

HTML changes are expensive.

There are more reasons for complex selectors. The developer may have used generic containers like <div> and <span> where elements with a stronger meaning would have been appropriate – HTML fragments should reflect the semantics of the content, not its presentational aspects. Or selectors were formulated verbosely, saying more than necessary about the matching HTML structure and its location.

In a large project, it is vital that the complexity of the product requirements that become visible to the user is restricted. Discussing the requirements, making architectural decisions, pair programming, commenting of the non-obvious, and code reviewing is indispensable for frontend engineering.

Reviewing the code does not end with the go-live of a product, but should be done throughout the product’s life cycle for keeping the application maneuverable. Frontend engineering needs to work closely together with the product and user experience teams in order to get a common understanding of the problem.

A selector analysis of large sites

At the end of 2009, 15 high-ranked sites and one CSS framework were examined for their style sheets’s mean selector specificity. The three place values a, b, c for specificity sum up distinct selector types in a selector. Therefore, the mean specificity of all CSS rules could qualify the complexity of the matching HTML fragments: the combined selector #footer .vcard img tells more about the HTML fragment than the single selector img does.

In order to calculate the mean selector specificity of a stylesheet, the CSS has to be syntactically understood (“parsed”). See addendum on how a parser was specially built for this task — Ruby source and further information on how to calculate the data is available on GitHub).

When the mean specificity data was plotted to Excel radar charts, three prototypical shapes could be identified. The area K under the curve of the entities a, b, c that span a triangle was calculated. This geometric value seems to reflect the “specificity expansion” at a glance: how complex is the usage of the selector specificity in this architecture?

Prototype Type 1
a = high, b = high, c = high
area K = large
Type 2
a = low, b = high, c = high
area K = medium
Type 3
a = low, b = high, c = low
area K = small
Chart Prototype 1 Prototype 2 Prototype 3
Means a = 0.636, b = 1.211, c= 0.955
K = 0.728
a = 0.203, b = 1.250, c= 1.010
K = 0.497
a = 0.110, b = 1.531, c= 0.461
K = 0.267

Discussion

The mean selector specificity, introduced in this paper, is computed on the selector specificity defined in CSS3. While a browser has to find one winning declaration, the calculation of the mean selector specificity keeps the specificity place values a, b, c distinct for summation.

Geometrically derived from the mean selector specificity, the area K of the charts is seen as a measure for the complexity of the selectors. The area K is large in type 1 and smallest in type 3, where class selectors (= b) dominate, in contrast to the sparingly use of type (= c) and ID selectors (= a). The OOCSS-framework is within this group, it was constructed by Nicole Sullivan to achieve highly reusable code patterns.

Would it be better (in terms of code reusability and maintainability) to choose a class-domination for the architecture of a large project? This could match a product design that aims for clarity: it forces all participants to think in patterns – instead of adding this and that here and there under certain circumstances only, and instead of making a functional complexity distractingly transparent to the user.

The release cycles the code has seen need to be taken into account: selectors of higher specificity may have been a pragmatic way to overrule older ones. Because CSS tends to shift towards higher specificity, the review of the selector architecture has to be integrated into the coding process.

None of the identified prototypical authoring styles is inherently good or bad. There are apparently very different ways of designing a frontend architecture with regard to composing the CSS selectors. This could be a starting point for a more in-depth analysis of the impact authoring styles have on the development of large applications.

Addendum

GitHub repository

The project’s code and further documentation are on GitHub.

Writing a CSS3 parser

See documentation on GitHub. The Ragel state machine compiler by Adrian Thurston constructs a state machine representation of a regular language — here, a modified and simplified CSS3 grammar. A Ruby command line application was written as a frame for the generated parser. It can handle console input, file input or hosted file input via http. The application’s output lists the selectors and their specificity and gives a summary of the mean specificity.

Unconsidered aspects of specificity

The style information varied from a hundred up to a few thousands of style rules. The CSS was included via HTML, imported via CSS or written into HTML-<style>-blocks. Often inline styles were added via the style-attribute of an element.

Note that inline styles, i.e. style information set by an element’s style attribute, were ignored in this analysis.

Text

The declarations have a specificity higher than any selector. Inline styles could – assuming a way would be found to weigh them – have an impact on the results. But “present” or “not present” doesn’t say if they become a maintenance nightmare (they often do), and counting them would require parsing the site’s delivered HTML after JavaScript is executed. Nevertheless, they cannot be easily set in relation to a style sheet’s selector results.

Combined selectors use combinators which express the relationship of the matching elements. The space between two simple selectors in the examples is the descendant combinator: table p represents any paragraph that is an arbitrary descendant of any table. Other combinators express the relationships more in detail: for example, the adjacent sibling combinator table + p represents the paragraph that is immediately following a table. The type of a combinator does not add to the specificity of selectors and was therefore not examined further. Combined selectors however can restrict a pattern to a very specific HTML fragment.

Some pseudo-classes like :nth-child() (which could be used to address the uneven rows of a table) are able to restrict the pattern further. They count like normal classes in the calculation of the selector specificity, but are much more specific with regard to the structure they represent.

About the author

Ingo ChaoIngo Chao is Manager Quality Assurance with a background as a front-end engineer at XING. He wrote a book on CSS, and he frequently contributes to the css-d mailing list. XING Profile »


3 thoughts on “CSS selector specificity and complexity in development

  1. I could not agree with you more. I work with a very large app and have stumbled upon this issue many times before. I always prefer to make a class as below

    .greenBorder{1px solid #00ff00}

    which I can reuse across the board and is intuitive than a combination of HTML elements to point to the final element to which the class will be applied to.

    Thank you for your post!

  2. Pingback: » CSS-Architektur The Codejet

Leave a Reply

Your email address will not be published. Required fields are marked *