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:

CSS Body Class - Folder Sample

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.

13 Comments

  • Jon,

    Great post. For a project I'm currently working on, I've decided to use a CSS framework, specifically, BlueprintCSS. I cannot say enough how impressed I am with the design of this framework. I actually learned more about CSS by using the framework that I would have trying to hand code CSS.

    When I go back to maintain old applications, I find myself throwing down a DIV tag and classes based on BlueprintCSS.

    If you haven't given it a good once over, you should.

    Otto

  • @Otto - Thanks, I'll definitely look into Blueprint CSS. How would you compare it to the Yahoo UI stuff?

  • A potentially better solution that avoids adding any extra markup to the is to include an additional CSS file containing styling specific to the current page in the . It's been a while but I think this can be achieved using MasterPages - in fact I'm sure it can.

    Since it' good practice to have styles in external files this solution probably keeps the markup cleaner than using Classes. The one drawback that I can think of is that you need the unique page CSS files to exist or some server code to check for the file's existence which is certainly possible.

    Good thought provoking post.

  • Jon,

    As always Great post and really good timing :)
    It has always been a fight for me to write CSS, I gain some experience but always search to improve and your writing on the topic is definitely a plus for me.
    I really enjoyed the idea of the API :)

    Laurent

  • It's not a better solution to add a unique-to-the-page CSS file, since then a) the CSS guy needs to put exception-rules into many different files if he needs to create them, and b) there's an extra request required to load the page.

    A CSS guy would probably find it more useful still (I realise that this was an off-the-cuff example!) if he had three classes to play with, instead of support-downloads-drivers - he could then target whole different parts of the hierarchy, as well as specific pages.
    The former:
    .support #content { /* rules for the support tree */ }
    .support.downloads.drivers #content { /* rules for the support-downloads-drivers */ page }

    Given that CSS is all about consistent application of rules, I mean - and I realise this is specifically to handle exceptions from consistency, but still ;)

  • >> 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.

    Too bad your tool dictates something like that. For the rest of us developers, php java ect, go with the ID over the class.

  • You can override the ID that ASP.NET adds (on the body at least).

    Something like:

    body.ID = "";
    body.Attributes.Add("id", "about-us");

    I've used this method many times. I expose two properties in my master page, BodyId and BodyClass, which any page can then update.

    Certainly use an ID over a class. But by all means combine them. Use an ID to style elements for a specific page (i.e. I want my headings blue on the home page, but other colours elsewhere), and classes for re-usable styles. This comes in useful where a page may use several layouts.

    For exmaple, one Master Page, outputs clean DIVs, but certain pages may use a different column layout. I can therefore create several layout classes, and shove them in the body class attribute to alter the layout of the page. Having a unique on the body ID attribute also means I can customise each instance of the page to suit.



    About Us page is an instance of my two-column layout.

  • Good post! In addition to what Nick said previously, I always add multiple browser classes to the body tag:
    eg.

    These are generated from the Browser property.
    This allows me to apply browser specific hacks by using specific rules indicating .IE-6 instead of relying on css hacks (* html xxx).

    Regards,
    Jelle

  • Very timely. Your explanation of the need for this is good and easy to follow. I've used various approaches for accomplishing this before but never really been happy with them. I ended up using the approach given by Nick in the comments and have been pleased with it so far.

  • For what it's worth, another good reason not to use an ID on the body tag is because older version of IE (including IE6, I believe) will not recognize it in your CSS. It'll recognize classes though.

    Regards...

  • Love it!
    They put things like “content” and “message” above the really important things, like clean consistent code.

  • Wir sind einer der Louis Vuitton Taschen Online Shop, wir billige Louis Vuitton Taschen bieten, kaufen Sie es auf unserer Louis Vuitton Outlet online!

  • Asking questions are really fastidious thing if you are not
    understanding anything fully, however this piece of writing presents nice understanding yet.

Comments have been disabled for this content.