hellorobot.org

Specificity Helps Put the Cascade in CSS

Why oh why, is that element displayed like that when I cleary wrote the CSS to display it like this?! Mostly, in these situations, the browser’s got it right, and I’m the bumbling doofus. Too many times, my error lies in a mishandling of CSS specificity.

I love that CSS allows for multiple class names, and I assign them often. It’s a great way to mark alternating rows in a table-like structure, for instance, or to mark up panels on a page that are exactly alike but for one attribute. But since two class definitions have equal specificity, the exact element reference and location within the CSS determines the output.

The Problem

Consider the following HTML:

<div id=”container”><div id=”center”><div class=”panel”><p>Content inside main panel.</p></div><div class=”hwrap”><div class=”panel”><p>Content inside plain panel.</p></div><div class=”panel red”><p>Content inside panel with .red class.</p></div></div></div></div>

With CSS in a single external file:

* {margin:0; padding:0; }#container {background:#666; text-align:center; width:100%; }#center {background:#555; border-left:1px solid #444; border-right:1px solid #444; margin:0 auto; padding:6px; text-align:left; width:240px; }.panel {background:#999; margin:0 0 6px; padding:1em 0.5em; }.hwrap {clear:both; }.hwrap .panel {background:#369; float:left; width:105px; }.red {background:red; margin-left:6px; }

This seems unnecessarily complicated, but I wanted to be certain to create the frame of a layout that isn’t unusual – I mean, something that actually would be used. It’s a basic centered layout with a primary content section and two footer-like sections.

Rendered sample HTMLEach of these panels (especially in a realized design) have very similar CSS markup – and rely on CSS’s cascade to keep from reiterating the same declarations. The page renders like the image to the left. The lower two panels are floated to share a single horizontal space; they are assigned a different background from the main whole-width panel – the default panel.

A final difference is the third background established for the last panel. It should be red, but it’s not. Why?

Firebug says that .hwrap .panel {background:#369; } overrides .red {background:red; } even though the second occurs on a later line in the stylesheet. I’m flummoxed until I realize the key is specificity: because the first declaration references two nested class names – .hwrap .panel – its specificity is higher, and it wins.

Some Solutions

1. The quick and dirty way is to add an !important to the last declaration: .red {background:red!important; }. But IE’s got some sketchiness about the !important declaration. Plus, this solution alters the rule’s weight when the problem was its specificity.

2. Better to raise the element’s specificity. I could delete the red class from the third panel and provide a red id: <div id=”red” class=”panel”>. This’ll raise the specificity, but I can’t re-use the “red” id on that page anymore. What if it’s a recurring background style?

Rendered sample HTML, CSS correctedHere’s the best of the fast solutions: I could change the style rule to begin .hwrap .red. Now I can re-use the red class if I have further panels down the page that share styling with it. Although this resolves the particular instance of the problem, it doesn’t stop me from making a specificity error again.

3. Another good solution is larger scale: my CSS could be more specific in its references. I can switch away from using a single class for panels that serve different purposes on the page: re-class the first panel “content,” the second “meta,” and the third “marginalia,” for example, assigning CSS specificity through specificity in naming. The altered CSS lines would look like this:

.content, .meta, .marginalia {background:#999; margin:0 0 6px; padding:1em 0.5em; }.meta, .marginalia {background:#369; float:left; width:99px; }.marginalia {background:red; margin-left:6px; }

It’s the exact same number of lines, and each panel’s markup is clearly defined. There’s little separation of content and design, but that line has to fall somewhere, right? They’re classes, so I can still reuse “meta,” “marginalia,” and “content” on the page for other panels.

But I think this method is confusing over time: the style hierarchy is obfuscated in the HTML – I can’t look at the html and see what elements are similar: I don’t know that the classes “content,” “meta,” and “marginalia” share any style attributes until I view the CSS. In the first markup, however, reusing the “panel” class clearly indicated a shared base style. CSS is powerful because it recognizes the object of the DOM – it styles the page in an OOP-aware manner. Each element is an object instantiating a class inheriting another object instantiating another class inheriting anther object… all the way up to the body, document, and window. The above markup eliminates an intuitive level of inheritance: there is no base “panel” class defined; instead three similar classes are defined laterally to each other.

I see a lot of markup that is very, very specific in class and ID names at the expense of inheritance. I think this CSS writing style is swimming against the current. Although, admittedly, this prevents the trouble I started out describing in this post, which is a simple mistake that’s caused me enough frustration enough times. But it’s a kind of brute force; I feel that it lacks elegance.

No Responses

Add your response

Respond to “Specificity Helps Put the Cascade in CSS”