Arm yourself with Drupal $body_classes!

A common question I get for theming Drupal sites is how to apply css to specific pages. For example, you might want your background image for your header to change on specific pages. Out of the box, Drupal doesn't give you any class in the HTML that's unique to your node to grab onto. This is where $body_classes come in.

Drupal 6 introduced the $body_classes variable to dynamically include certain information about the page you are on as a class of your body tag. These include:

- .no-sidebar/.one-sidebar/.two-sidebars
- .sidebar-left/.sidebar-right
- .front/.not-front
- .logged-in/.not-logged-in
- .page-[PAGE TYPE]
- .node-type-[CONTENT TYPE]

Some starter themes, like Zen, come with the $body_classes variable already in place. If you are building your theme from scratch or using a theme that doesn't have it, you'll need to add it in.

To add $body_classes to your theme, open your page.tpl.php file, and find your body tag. Edit it to be: <body class="<?php print $body_classes ?>">. Now, when you check your HTML, your body tag should look something like this:

<body class="not-front logged-in page-node node-type-forum one-sidebar sidebar-right">

You can probably see how these will give someone building a theme good stuff to work with. However, sometimes this isn't enough; sometimes you need node-specific classes. With Drupal 6, you can use a preprocess_page function to add on to your $body_classes variable.

First, you will need to create a function that properly takes your URL and converts it to a class (you don't want any space, capitals or irregular characters). Open your template.php file and add the following function:

<?php
/**
* Converts a string to a suitable html ID attribute.
*
* <a href="http://www.w3.org/TR/html4/struct/global.html#h-7.5.2" title="http://www.w3.org/TR/html4/struct/global.html#h-7.5.2" rel="nofollow">http://www.w3.org/TR/html4/struct/global.html#h-7.5.2</a> specifies what makes a
* valid ID attribute in HTML. This function:
*
* - Ensure an ID starts with an alpha character by optionally adding an 'id'.
* - Replaces any character except A-Z, numbers, and underscores with dashes.
* - Converts entire string to lowercase.
*
* @param $string
*   The string
* @return
*   The converted string
*/
function YOURTHEME_id_safe($string) {
 
// Replace with dashes anything that isn't A-Z, numbers, dashes, or underscores.
 
$string = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
 
// If the first character is not a-z, add 'n' in front.
 
if (!ctype_lower($string{0})) { // Don't use ctype_alpha since its locale aware.
   
$string = 'id' . $string;
  }
  return
$string;
}
?>

This is reusable code - use it anywhere you need to convert a string into an id.

Next, search for a section that starts with YOURTHEME_preprocess_page. If you don't have one, just add in this code anywhere in the file:

<?php
/**
* Intercept page template variables
*
* @param $vars
*   A sequential array of variables passed to the theme function.
*/
function YOURTHEME_preprocess_page(&$vars) {

  if (!

$vars['is_front']) {
   
// Add unique classes for each page and website section
   
$path = drupal_get_path_alias($_GET['q']);
    list(
$section, ) = explode('/', $path, 2);
   
$vars['body_classes'] .= ' ' . YOURTHEME_id_safe('page-' . $path) . ' ';
   
$vars['body_classes'] .= ' ' . YOURTHEME_id_safe('section-' . $section) . ' ';
  }
}
?>

Otherwise, you will need to merge this code into the preprocess_page function.

This code gives you two new classes: page-PATH and section-SECTION. It might look something like: <body class="not-front logged-in page-node node-type-forum one-sidebar sidebar-right page-about-history section-about">. The page-PATH class displays the full url path, and the section is just the first arg in the path - enabling you to do some section-based theming, if your URLs are all segmented properly.

The final trick is to add your taxonomy terms to $body_classes. After the new section in your preprocess_page function where you turn urls into your $body_classes, add this snippet:

<?php
if (isset($vars['node']->taxonomy)) {
 
// Add unique classes based on taxonomy terms
 
$taxonomy_classes = array();
  foreach(
$vars['node']->taxonomy as $term_info) {
   
$taxonomy_classes[] = 'taxonomy-'.YOURTHEME_id_safe($term_info->name);
  }
 
$vars['body_classes'] .= " ".implode(' ', $taxonomy_classes); // Concatenate with spaces
}
?>

This will add a class to your body tag that looks like taxonomy-YOURTAG. All together, your preprocess_page function might look like this:

<?php
/**
* Intercept page template variables
*
* @param $vars
*   A sequential array of variables passed to the theme function.
*/
function YOURTHEME_preprocess_page(&$vars) {

  if (!

$vars['is_front']) {
   
// Add unique classes for each page and website section
   
$path = drupal_get_path_alias($_GET['q']);
    list(
$section, ) = explode('/', $path, 2);
   
$vars['body_classes'] .= ' ' . YOURTHEME_id_safe('page-' . $path) . ' ';
   
$vars['body_classes'] .= ' ' . YOURTHEME_id_safe('section-' . $section) . ' ';
  }
 
  if (isset(
$vars['node']->taxonomy)) {
   
// Add unique classes based on taxonomy terms
   
$taxonomy_classes = array();
    foreach(
$vars['node']->taxonomy as $term_info) {
     
$taxonomy_classes[] = 'taxonomy-'.YOURTHEME_id_safe($term_info->name);
    }
   
$vars['body_classes'] .= " ".implode(' ', $taxonomy_classes); // Concatenate with spaces
 
}
}
?>

Armed with those $body_classes, there is very little page- and section-specific CSS targeting you can't do!

Anonymous wrote 13 weeks 8 hours ago

This is sooooooooo timely! I've got to do a project that has different css for different pages...I hope a non-php noob can make it happen! thanks for serendipitous moment!

Antti Ahti wrote 13 weeks 2 hours ago

I needed to customize a node form so I did it like this. The node form appears when you add a new node or edit an existing node.

<?php
if (arg(0) == 'node') {
  if (
arg(1) == 'add' && arg(2)) {
   
$node_type = arg(2);
  } else if(
is_numeric(arg(1)) && arg(2) == 'edit') {
   
$node_type = $vars['node']->type;
  }
  if (
$node_type) {
   
$vars['body_classes'] .= ' node-edit-form ';
   
$vars['body_classes'] .= ' ' . YOURTHEME_id_safe('node-edit-form-' . $node_type) . ' ';
  }
}
?>
Dave Hansen-Lange wrote 12 weeks 6 days ago

creating YOURTHEME_id_safe() might not necessary. You can just use the core function
form_clean_id() instead which also has the advantage of not creating duplicate IDs.

Ryan wrote 11 weeks 2 days ago

Perfect! I'm not sure if Dave Hansen-Lange had a better idea with the form_clean_id() ... I know nothing about this stuff. But I was still able to copy everything you had and get just what I was looking for. Thanks!

Amanda Luker wrote 11 weeks 1 day ago

Great! Glad this has been helpful to people. Dave, I will certainly give that form_clean_id() core function at try in the future -- I wasn't aware of it!

Amanda

Contact Us

About Amanda Luker

Amanda Luker is a web designer in Minneapolis, MN.

Advomatic on Twitter