Advanced Drupal 7 Form Element Theming

Posted on Feb. 28, 2013, 6:22 p.m.

Ever wanted to give form elements a custom look? Theming entire forms is straightforward, if laborious. But theming individual textboxes, checkboxes and radio buttons is slightly more obscure. Keep reading to find out how to fully customize your form elements, from the input itself to the label.

You might be wondering: can't I just implement a theme_checkbox() function? Well, that might meet your requirements, but this doesn't handle the label at all. Let's set up our theme to give us maximum flexibility.

Learning Drupal? Subscribe to my Drupal articles, tips and tutorials.

Let's start with a use case: Bootstrap likes to have checkboxes inside the label element. I happen to like this markup too. But Drupal places the checkbox next to the label. How can we achieve this without hacking core?

Start by copying over theme_form_element() into the template.php file in your theme. Let's assume the theme is called example. You can find the body here. Be sure to replace the word theme with example.

Now find the switch statement and replace it to look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
if ($element['#type'] == 'checkbox') {
  $variables['rendered_element'] = ' ' . $prefix . $element['#children'] . $suffix . "\n";
  $output .= theme('form_element_label', $variables);
}
else {
  switch ($element['#title_display']) {
    case 'before':
    case 'invisible':
      $output .= ' ' . theme('form_element_label', $variables);
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;

    case 'after':
      $output .= ' ' . $prefix . $element['#children'] . $suffix;
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
      break;

    case 'none':
    case 'attribute':
      // Output no label and no required marker, only the children.
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;
  }
}

Notice we wrapped it with an outer if statement. Here, we a define custom behavior for our checkbox. This allows us to give checkboxes special handling for labels using a custom theme_form_label(). Let's implement that, by copying the existing theme_form_element_label() from here and changing theme to example. But let's make a change. Remove the return statement (and the comment above it) and replace it with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
// Bootstrap wants us to add a class to the label as well.
if ($element['#type'] == 'checkbox') {
  $attributes['class'] = 'checkbox';
}

// The leading whitespace helps visually separate fields from inline labels.
if (!empty($variables['rendered_element'])) {
  return ' <label' . drupal_attributes($attributes) . '>' . $variables['rendered_element'] . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
}
else {
  return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
}

Last but not least, Bootstrap also likes us to add the class checkbox to the input element itself. Though this isn't necessary for any CSS rules, the guide shows the markup this way. So it's best to be safe. We do this by overriding the theme_checkbox() function. Change the _form_set_class() function call to:

1
2
<?php
_form_set_class($element, array('form-checkbox', 'checkbox'));

And that's it. You now have a fully themed Bootstrap checkbox. More importantly, adding future elements and theming is very quick. You only need to set up all these theme functions once.

Check out the full template.php file for reference.