Disclaimer

I searched all throughout Drupal to find a solution for this problem, but failed. Therefore, I was forced to hack my own. I am very new to PHP, so if there is a better way to do this, please share. This is just what I found to work for me. Also, I hope I'm posting this in the right section. :-)

Background

A label defines a string of text that is associated with a particular form element. When a user clicks on this text, the focus is set to the associated element. Drupal automatically creates labels for each element in a form.

The Problem

The labels that Drupal generates are applied two different ways. For textfields, textareas, etc. they are set up with the label before the form element. The for attribute of the label corresponds to the id of the input element:

<label for="element-id">Field Name:</label>
<input type="text" id="element-id" />

For checkboxes and radios, the form element and the text for the label are nested within the label tag itself. No for attribute is applied to the label, and no id is created for radio buttons. (Though there is an id created for checkbox inputs, it is useless for this purpose w/out having the for attribute on the label):
<label><input type="checkbox" />Check this box!</label>

This is perfectly valid, and there shouldn't be any usability problems. However, Internet Explorer does not recognize a label unless it specifically has the for attribute associated with an element's id. This means that when using IE, clicking on the label text for a checkbox or radio button does NOT focus to the desired element; it does nothing, and you must actually click on the checkbox or radio button itself. Labels for anything besides checkboxes and radios work fine though.

The Solution

In order to force Drupal to apply the for attribute for checkboxes and radios, we're going to have to modify 'form.inc' which is located in the 'includes' directory.

  1. Open 'form.inc' in a text-editor
  2. Scroll to line 1290 (This is where checkboxes and their labels are created):
    <?php
    function theme_checkbox($element) {
     
    _form_set_class($element, array('form-checkbox'));
     
    $checkbox = '<input ';
     
    $checkbox .= 'type="checkbox" ';
     
    $checkbox .= 'name="'. $element['#name'] .'" ';
     
    $checkbox .= 'id="'. $element['#id'].'" ' ;
     
    $checkbox .= 'value="'. $element['#return_value'] .'" ';
     
    $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
     
    $checkbox .= drupal_attributes($element['#attributes']) . ' />';
      if (!
    is_null($element['#title'])) {
       
    $checkbox = '<label class="option">'. $checkbox .' '. $element['#title'] .'</label>';
      }
      unset(
    $element['#title']);
      return
    theme('form_element', $element, $checkbox);
    }
    ?>

    Note that an id is applied to the checkbox, but no for is applied to the label.
  3. Replace the above function with the following:
    <?php
    function theme_checkbox($element) {
     
    _form_set_class($element, array('form-checkbox'));
     
    $checkbox = '<input ';
     
    $checkbox .= 'type="checkbox" ';
     
    $checkbox .= 'name="'. $element['#name'] .'" ';
     
    $checkbox .= 'id="'. $element['#id'].'" ' ;
     
    $checkbox .= 'value="'. $element['#return_value'] .'" ';
     
    $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
     
    $checkbox .= drupal_attributes($element['#attributes']) . ' />';
      if (!
    is_null($element['#title'])) {
       
    $checkbox = '<label for="'. $element['#id'] .'" class="option">'. $checkbox .' '. $element['#title'] .'</label>';
      }
      unset(
    $element['#title']);
      return
    theme('form_element', $element, $checkbox);
    }
    ?>

    The important part, and the only part we actually changed, was adding the for attribute and setting its value to the element id that was declared earlier.
  4. That's all you have to do for the checkboxes! Now that the label is associated with the checkbox, clicking on the label text will select the checkbox in all browsers for all checkboxes on your Drupal site. :-)

BUT WAIT! What about the radio buttons? Well those are a little bit trickier since no id is even created for them by Drupal. Here's how to do it.

  1. Scroll to line 1053 of 'form.inc' (This is where radio buttons are created):
    <?php
    function theme_radio($element) {
     
    _form_set_class($element, array('form-radio'));
     
    $output = '<input type="radio" ';
     
    $output .= 'name="' . $element['#name'] .'" ';
     
    $output .= 'value="'. $element['#return_value'] .'" ';
     
    $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
     
    $output .= drupal_attributes($element['#attributes']) .' />';
      if (!
    is_null($element['#title'])) {
       
    $output = '<label class="option">'. $output .' '. $element['#title'] .'</label>';
      }
      unset(
    $element['#title']);
      return
    theme('form_element', $element, $output);
    }
    ?>

    Note that there is no id created for the radio buttons, and no for created for the label.
  2. Replace the above function with the following:
    <?php
    function theme_radio($element) {
     
    _form_set_class($element, array('form-radio'));
     
    $output = '<input type="radio" ';
     
    $output .= 'name="' . $element['#name'] .'" ';
     
    $output .= 'id="'. $element['#id'] .'-'. $element['#return_value'] .'" ' ;
     
    $output .= 'value="'. $element['#return_value'] .'" ';
     
    $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
     
    $output .= drupal_attributes($element['#attributes']) .' />';
      if (!
    is_null($element['#title'])) {
       
    $output = '<label for="'. $element['#id'] .'-'. $element['#return_value'] .'" class="option">'. $output .' '. $element['#title'] .'</label>';
      }
      unset(
    $element['#title']);
      return
    theme('form_element', $element, $output);
    }
    ?>

    Note that we first set an id for the input. However, we cannot simply create a single id because then the same id would be applied to all checkboxes and labels within a radio group, causing all labels to point to the first radio button.
  3. Luckily, radio buttons have a numerical and incremental value associated with them. (value="0", value="1", value="2" for the first, second, and third radio button within a group, and so on). We use this feature to append the value to the end of the id for each radio button, giving each one a unique id (id="element-id-0", id="element-id-1", etc.)
  4. Now we do the same for the for attribute of the label, and we're all done!
  5. Conclusion

    So now any time a checkbox or radio button is created anywhere within your Drupal site, they will have a label attached to them that works properly in all browsers. Thank you for reading, and please share your comments or suggestions. :-)

Comments

There's no need to modify form.inc to acheive this. Take a look at the function names, they all begin with theme_ which tells you these functions can be overriden at the theme layer.

See http://drupal.org/node/55126 on how to override theme functions in Drupal.

inputs inside labels cause weird problems if you put onclick events on the labels - they get fired twice. caused me some trouble to debug