Advanced Drupal form theming: Take control of error styling with a form-item-error class

Note: This HOWTO covers Drupal 6.x.

By default, Drupal adds an .error class to the form element itself: textarea, select, input, and so on. Sometimes, that’s not good enough. Maybe a client needs the label’s color changed — or a big, red border encompassing both the label and input elements.

This can be achieved by overriding theme_form_element() to add an error class to div.form-item, the div that wraps all elements in a form.

Add these lines to the top of your theme_form_element() override:

function MYTHEME_form_element($element, $value) {
  // This is also used in the installer, pre-database setup.
  $t = get_t();
 
  // Start compiling classes for the .form-item wrapper
  $classes[] = 'form-item';
 
  // Add an error class to the .form-item wrapper
  $exempt_elements = array('checkbox', 'radio', 'password_confirm');
  if (form_get_error($element) && !in_array($element['#type'], $exempt_elements)) {
    $classes[] = 'form-item-error';
    $classes[] = 'form-item-error-'. $element['#type']; // Optional
  }
 
  // Build the .form-item wrapper
  $output = '<div class="'. implode(' ', $classes) .'"';
  if (!empty($element['#id'])) {
    $output .= ' id="'. $element['#id'] .'-wrapper"';
  }
  $output .= ">\n";

The lines above should replace the following:

function theme_form_element($element, $value) {
  // This is also used in the installer, pre-database setup.
  $t = get_t();
 
  $output = '<div class="form-item"';
  if (!empty($element['#id'])) {
    $output .= ' id="'. $element['#id'] .'-wrapper"';
  }
  $output .= ">\n";

All form elements except those listed in the $exempt_elements array will have two classes applied to div.form-item: .form-item-error and .form-item-error-ELEMENT_TYPE. Feel free to change these as you like.

Why are some elements exempt? Checkboxes and radio button usually come in groups of two or more wrapped inside a single div.form-item, and each individual box or button is wrapped in yet another div.form-item. This nesting can make theming difficult, so I’ve exempted them here. (You can, of course, customize this override to fit your needs.)

Here’s what the markup looked like before adding wrapping error classes. Note that the .error class is applied to the form elements themselves only, which makes theming some elements like radio buttons impossible in some browsers:

<div class="form-item" id="edit-first-name-wrapper">
  <label for="edit-first-name">First name: <span class="form-required" title="This field is required."><em></span></label>
 
  <input maxlength="255" name="first_name" id="edit-first-name" size="60" value="" class="form-text required error" type="text" />
</div>
 
<div class="form-item" id="edit-last-name-wrapper">
  <label for="edit-last-name">Last name: <span class="form-required" title="This field is required."></em></span></label>
 
  <input maxlength="255" name="last_name" id="edit-last-name" size="60" value="" class="form-text required error" type="text" />
</div>
 
<div class="form-item">
  <label>Gender: <span class="form-required" title="This field is required.">*</span></label>
 
  <div class="form-radios"><div class="form-item" id="edit-gender-m-wrapper">
  <label class="option" for="edit-gender-m"><input id="edit-gender-m" name="gender" value="m" class="form-radio error" type="radio" /> Male</label>
  </div>
 
  <div class="form-item" id="edit-gender-f-wrapper">
  <label class="option" for="edit-gender-f"><input id="edit-gender-f" name="gender" value="f" class="form-radio error" type="radio" /> Female</label>
  </div>
 
</div>

And here’s the same markup after applying the override above:

<div class="form-item form-item-error form-item-error-textfield" id="edit-first-name-wrapper">
  <label for="edit-first-name"><span class="form-required" title="This field is required."><em></span> First name:</label>
 
  <input maxlength="255" name="first_name" id="edit-first-name" size="60" value="" class="form-text required error" type="text" />
</div>
 
<div class="form-item form-item-error form-item-error-textfield" id="edit-last-name-wrapper">
  <label for="edit-last-name"><span class="form-required" title="This field is required."></em></span> Last name:</label>
 
  <input maxlength="255" name="family_name" id="edit-last-name" size="60" value="" class="form-text required error" type="text" />
</div>
 
<div class="form-item form-item-error form-item-error-radios">
  <label><span class="form-required" title="This field is required.">*</span> Gender:</label>
 
  <div class="form-radios"><div class="form-item" id="edit-gender-m-wrapper">
  <label class="option" for="edit-gender-m"><input id="edit-gender-m" name="gender" value="m" class="form-radio error" type="radio" /> Male</label>
</div>
 
<div class="form-item" id="edit-gender-f-wrapper">
  <label class="option" for="edit-gender-f"><input id="edit-gender-f" name="gender" value="f" class="form-radio error" type="radio" /> Female</label>
</div>

The wrapping .form-item divs now have error classes: form-item form-item-error form-item-error-textfield.

The technique described above was inspired by the function _form_set_class(), which is responsible for adding .required and .error classes to form elements.

Commenting on this Blog post is closed.

Todd Ross Nienkerk
Todd Ross Nienkerk is a co-founder of Four Kitchens and was born in a subterranean cave in the future.

Comments

Thanks, so useful !

You can also add a ‘form required’ class to the form item:

if(!empty($element['#required'])) {$classes[] = 'form-item-required';}

it could be useful too for some client’s needs.

opi