Using <body> Classes To Fight CSS Class Explosion
I previously wrote about taking CSS beyond a simple style library by writing HTML that’s easy to style. I’d like to go into one point in a bit more detail – avoiding class explosion by leveraging descendant selectors:
You might think that really stylable HTML needs classes all over the place. That's not true, thanks to descendant selectors, which let you target elements inside a parent element. For instance, descendant selectors will let you style all <a> elements which appear inside a <div> with and id of "nav":
div#nav a { font-weight:bold; }
This is great because we're able to target specific elements (only <a> tags inside <div id="nav">) without a lot of extra work or code.
Act 1. The Simple Plan
Things start out simple enough – we lay out all the content for a site, match it against the awesome site design we’ve been provided, and set up a masterpage and consistent style rules for our site. For instance, we’ll have a decide that <h2> headings in the main content area should be big and dark green:
div#maincontent h2 { font-size: 1.4em; color: #090; }
…and we pat ourselves on the back. This stuff’s easy!
Act 2. Introducing the Villain: Reality
In the real world, clients tend to get their priorities all messed up. They put things like “content” and “message” above the really important things, like clean consistent code. We’re two days away from launch, and they decide that the headings on two pages in the site should be smaller and a slightly different color to match with the other content. Well, it does look a little better, but it doesn’t fit with our style rules at all.
Act 3. The Conflict
At this point, we’ve got a choice. We can create a custom class:
<h2 class=”specialHeading”>
That’s sometimes the right answer, but it can lead to a class explosion – if we head down this road, we’ve got tons of custom classes for every exception, which makes our CSS harder to manage and clutters up our HTML with piles of non-informational goo, which is exactly what we’re hoping to get away from with CSS and semantic HTML.
What’s the alternative? Body classes, like this:
<body class=”about-us”> <div id="maincontent"> <h2>Sample Text</h2> </div> </body>Now we can write a selector in our CSS which targets that page specifically:
div#maincontent h2 { font-size: 1.4em; color: #090; } /* This rule sets the default for the site */ body.about-us div#maincontent h2 { font-size: 1.1em; color: #161; } /* This rule overrides the default on the About Us page */
Of course, we’ve got a draw at this point – we added a class to the body instead of a div, so the improvement’s not as obvious. But that’s just the sample code scenario talking here – in real life we’d have more complex HTML on each page, and the client requests would come in fast and furious. Body classes scale beautifully, because setting that one class allows us to override site style rules for any page in the site.
Act 4. Do Try This At Home
If you’re using ASP.NET and taking advantage of the Master Pages feature, you can easily add page-specific classes with a minor tweak to your Master Page. First, set your master page’s Body tag to run as an HTML Generic Control by assigning it an ID and slapping the old runat=”server” attribute in there:
<body id="Body" runat="server">
Now we’ll add the following code to the Master Page’s code behind:
protected void Page_Init(object sender, EventArgs e) { SetBodyCssClass(); } private void SetBodyCssClass() { string pageType = Page.GetType().Name; pageType = pageType.Replace("_aspx", string.Empty); pageType = pageType.Replace('_', '-'); string bodyClass = Body.Attributes["class"] + " " + pageType; Body.Attributes["class"] = bodyClass.Trim(); }
That’s it. This will write out page classes that are unique, taking into account folder paths as well. For example, let’s look at the following site structure:
Here I’m using a Default.aspx page in every subfolder, so we can navigate to sample.com/Products/ or sample.com/Support/ and the Default.aspx page in that subfolder will be displayed. In the case of the Default.aspx page in the Products folder, the body tag would get this class: products-default. For sample.com/Support/Downloads/Drivers.aspx, we’d have the class support-downloads-drivers:
<body id="ctl00_Body" class="support-downloads-drivers">
You can of course change this to fit with how you structure your sites, but this seems simplest from a maintenance point of view – it’s obvious what class you’ll be expecting given a URL.
The code’s pretty simple. Each compiled page has a declared type that’s named based on the folder path and the page filename (e.g. Default.aspx). That works despite the fact that the code is declared in a Master Page, since a Master Page is really a User Control that’s injected into the page rather than the other way around. So when our code executes for /About/Default.aspx, Page.GetPage().Name will return about_default_aspx. We’re just trimming the _aspx from the end and converting the underscores to dashes because they’re more readable. The other thing to notice is that we’re not just replacing the class attribute, we’re appending a new one, since you can (and frequently should) assign more than one class to an HTML element, separated by spaces, like this: <div class=”callout bio about-default”>. So it’s important to append a class rather than overwrite the old one.
Note that I’m not recommending that you slap this code into your site two days from launch when content changes require it – you should do it early in your project, so that you can make use of the body classes throughout the development cycle.
Why A Class Instead Of an ID?
Good question. In general, when you’re working with something that will only appear once on a page, you’d want to use an ID rather than a Class, since an HTML ID attribute is (by definition) unique to the page. I went with the Class in this case because it’s simpler – ASP.NET munges ID’s in server controls to make sure they’re unique (turning body id=”body” into body id=”ctl00_body”, as seen above), but doesn’t do anything to classes.