DiggingIntoWordPress

by Chris Coyier & Jeff Starr

Advanced WordPress Targeting with body_class_plus()

Posted by on

Since WordPress 2.8, we can target specific types of page views with CSS using the new body_class() tag. Designed for use within the <body> element, body_class() outputs various class attributes according to the current type of page view. This makes it easy to apply page-specific styling such as current-page navigation highlighting and other nifty CSS tricks.

Strengths and shortcomings of the body_class() tag

For the uninitiated, here is a quick review of the body_class() tag. When used as follows:

<body <?php body_class(); ?>>

The body_class() tag will output the following class names depending on current page type:

  • .home — class name output for the home page
  • .archive — class name output for any archive page
  • .category — class name output for any category page
  • .search — class name output for any search page
  • .tag — class name output for any tag archive page
  • Plus many more..

So, for example, when you are logged in to WordPress and viewing your blog’s home page, the body_class() tag will result in something like this:

<body class="home blog logged-in">

Or, if you were logged out and visiting your “Asides” category archive, your source code would include this:

<body class="archive category category-asides">

This functionality enables you to, say, apply styles to the <h1> heading elements on all of your search pages:

body.search h1 {
	border-bottom: 1px solid #ffff99;
	color: #ffff99;
	}

Likewise, the class names output by the body_class() tag make it possible to target conditionally any element on any type of page, thereby greatly increasing the stylistic flexibility of WordPress. Yet despite its awesomeness, body_class() is essentially an “all-or-nothing” proposition. We can apply styles to all category pages, all search pages, all page pages, and so on. We can even use CSS chaining to target all category views when the user is logged in:

.home.logged-in { border: 100px solid red; }

This is powerful, yes, but what if we want to target, say, all categories that include the term “services” in the category slug? Sure, we could declare each of them individually, like this:

.category-coffee-service { color: red; }
.category-brunch-services { color: red; }
.category-dinner-services { color: red; }
.category-snacks-services { color: red; }
.category-drinks-services { color: red; }
.category-inside-services { color: red; }
.category-design-services { color: red; }
.category-health-services { color: red; }
.category-services-info { color: red; }
.category-services-data { color: red; }
.category-services-cost { color: red; }

But that’s pretty inefficient. Plus, what if the user/client needed to create new “services-related” categories? Have fun explaining to them how edit the CSS file with the appropriate rules.

We could, I suppose, use CSS-3’s wildcard substring-matching attribute-selector:

body[class*="services"] { color: red; }

Definitely cleaner, but there a significant number of browsers that do not support CSS 3, so this is less than an ideal solution.

A better solution would be to add pattern-matching functionality to the body_class() tag. After all, body_class() keeps track of just about every possible type of page-view available on your site. So why not filter the output of body_class() for any instances of “services” and output a specific class-attribute for all matching pages. This would allow us to target and apply styles to virtually any subset of page-views, including those new “services-related” categories for our client site. Say “goodbye” to manual code updating and say “hello” to automated advanced targeting with body_class_plus().

Advanced targeting with body_class_plus()

The body_class_plus() tag extends the functionality of body_class() by using PHP’s output buffering to conditionally evaluate its contents. This functionality enables us to pattern-match for specific phrases, and execute conditional operations based on the contents of body_class().

Here is the body_class_plus() function that may be placed in your functions.php file:

Update: There is a new & improved version of this code available via plugin!

<?php // http://digwp.com/2009/08/wordpress-body-class-plus/
function body_class_plus() { 
	if(function_exists('body_class')) { 
		ob_start(); body_class(); 
		$class = ob_get_contents(); 
		ob_end_clean(); 
		if($class) {
			if(preg_match("/services/i", $class)) { 
				echo 'class="services"';
			} elseif(preg_match("/products/i", $class)) {
				echo 'class="products"';
			} elseif(preg_match("/business/i", $class)) {
				echo 'class="business"';
			} elseif(preg_match("/pleasure/i", $class)) {
				echo 'class="pleasure"';
			} else {
				echo 'class="no-body-class-matches"';
			}
		} else { 
			echo 'class="body-class-not-available"'; 
		} 
	}
} ?>

To call this function in your theme template, include the following tag in the element of your choice (e.g., within the <body> element):

<?php if(function_exists('body_class_plus')) { body_class_plus(); } ?>

This is a exemplified version of the body_class_plus() function that enables us to target and apply specific class attributes to any page or category containing the following terms in the URL/page-slug:

  • services” — will apply class="services"
  • products” — will apply class="products"
  • business” — will apply class="business"
  • pleasure” — will apply class="pleasure"

This makes it easy to match specific groups of categories, pages, posts, or any combination thereof. Even better, the body_class_plus() function makes it possible to do more than apply classes to targeted page types. In addition to, or instead of, applying classes to specific groups of pages, you can implement any type of functionality desired. For example, you could display template tags, echo content, or execute custom scripts depending on page type. The possibilities are endless.

Download the plugin

If custom functions aren’t your thang, and you would rather implement this functionality via plugin, you can grab it here:

Thanks to Peter Kahoun for contributing his expertise and improving the functionality of this plugin.

Take-home message

The take-home message is that the body_class_plus() function extends the functionality of the body_class() tag to target and apply styles to any particular subset or combination of page types. Further, the body_class_plus() function takes body_class() beyond the realm of CSS, making it possible to apply template tags, PHP, and other scripts to virtually any custom-defined set of pages and page types.

Complete list of body_class() attribute values

Here is a complete list of the available classes for the body_class() tag:

  • rtl
  • home
  • blog
  • archive
  • date
  • search
  • paged
  • attachment
  • error404
  • single postid-(id)
  • attachmentid-(id)
  • attachment-(mime-type)
  • author
  • author-(user_nicename)
  • category
  • category-(slug)
  • tag
  • tag-(slug)
  • page-parent
  • page-child parent-pageid-(id)
  • page-template page-template-(template file name)
  • search-results
  • search-no-results
  • logged-in
  • paged-(page number)
  • single-paged-(page number)
  • page-paged-(page number)
  • category-paged-(page number)
  • tag-paged-(page number)
  • date-paged-(page number)
  • author-paged-(page number)
  • search-paged-(page number)

For more ways to dynamically set body IDs, check out my article, 9 Ways to Set Dynamic Body IDs via PHP and WordPress.

16 Responses

  1. wonderfull tips.. thanks..

  2. Wow, that is great. Really nice!

    I’ll definitely be trying that out on my website (before on any others). So far, I had counted on a bunch of PHP conditionals (if on page ID X, show this class of a button), but I see that this could remove a bunch of that code (instead of that conditional comment, it would be possible to say “.pageX .button” and so on).

  3. Hi Jeff,
    thanks again for sharing this, nice enhancement :-)
    I think it could be made a wee bit more flexible if the “search” was not hard-wired, but used as argument(s) for the function-call like

    body_class_plus('service','products','pleasure');

    or

    body_class_plus($search=array('service','products','pleasure'));

    and the doing something like

    ... foreach ($search as $sclass){
    $s = "/" . $sclass . "/i";
    if preg_match($s, $class)) {
    echo 'class="'.$sclass.'"';
    } ...

    What do you think?
    Best,
    Tom

  4. You da man Jeff!

    L

  5. Darfuria August 17, 2009

    Brilliant. I developed a website that had different link colours and border colours on various pages and sub-pages. I eventually ended up doing a crazy URL explode and making stylesheets in my theme directory with the same name as the pages. Very dodgily written code.

    This will work much better :)

  6. @Darfuria Haha, yea, i was gonna do that. Didn’t have the guts in the end.

  7. There is body_class hook (filter). Much better. And preg_match() isn’t neccessary here, strpos() works faster. Isn’t that right?

    • Jeff Starr

      I tried using the body_class filter, but couldn’t get the PHP output buffering to play along. PHP is not my first language, so admittedly there are probably better ways of writing this code. I was actually hoping that some PHP wizards would help to improve the function.

    • Jeff Starr

      Awesome. I have uploaded the new version and updated the post accordingly. Later this week I will see about making it easier to add/remove classes and the matched strings. Thanks for the help!

  8. I had a need to assign a body class to all pages, based on their top-most parent page. So, all children and grandchildren of /top-page/ would get the “top-page” body class.

    I created this function in functions.php. It grabs the slug of the top most ancestor:

    function my_get_top_ancestor() {

           global $post;

           if ($post->post_parent) {
                  $ancestors=get_post_ancestors($post->ID);
                  $root=count($ancestors)-1;
                  $parent = $ancestors[$root];

           } else {
                  $parent = $post->ID;
           }

           $parentpost = get_post($parent);
           $parentslug = $parentpost->post_name;

           return $parentslug;

    }

    Then in the tag, I have

    <body <?php body_class(my_get_top_ancestor()); ?>>

  9. I added your function to my theme functions file and changed the body link as instructed.

    Now an example body tag from a page on my site is:

    Here is what I am trying to do, that I can’t figure out.
    I want to apply a different stylesheet to the ‘featured-articles’ category.

    Could you provide some example css or whatever is needed so that I can apply new styles to just that category?

    I’m trying to do things like change the page background image, change the font link colors etc.. pretty basic.

    I don’t know what you think about it, but I think it would be a great ‘extension article’ to this one. Like, now you have body class….. here is how to use it, a newbie guide.

    thanks

    • Jeff Starr

      One way to do this would be via conditional tag:

      <?php if(is_category('33')) { ?>

      <link href="http://domain.tld/path/special.css" rel="stylesheet" type="text/css" media="screen" />

      } ?>

      Place in the header of your document and edit to taste.

Comments are closed. Contact us with any critical information. Thank you!

Code is poetry