Adding Descriptions to Drupal 7 Checkboxes and Radios

Recently, I was creating a form that provided a list of options as checkboxes and needed to include helper text for each individual checkbox. While the Form API in Drupal 7 has a #description attribute, for checkboxes and radios it applies that as text for the entire group. After a lot of looking, there didn't seem to be a way that allowed for passing descriptions into each item in the #options array that is expected. I discovered the FAPI's #after_build callbacks, which turned out to be just the solution I needed.

Defining an #after_build callback

The FAPI documentation describes #after_build pretty well: “An array of function names which will be called after the form or element is built.” This allows us to make further alterations to our form elements after they're processed by Drupal, but before they're rendered to HTML. In the case of checkboxes and radios, the FAPI elements for each individual checkbox/radio are inserted by Drupal during the initial form build. That means in order to alter those specific elements, we'll need to access the form array after the initial build. Hence, #after_build.

To get started, add some #after_build callbacks to your element. This can be done either through a new form element definition (as shown below), or by adding new elements to #after_build through hook_form_alter().

$form['form_checkboxes'] = array(
  '#type' => 'checkboxes',
  '#title' => t('My Checkboxes'),
  '#options' => array(
    'option_1' => 'Option 1',
    'option_2' => 'Option 2',
  '#options_descriptions' => array(
    'option_1' => 'Description 1',
    'option_2' => 'Description 2',
  '#default_value' => variable_get('form_checkboxes', array()),
  '#after_build' => array('_option_descriptions'),

This will call a function named _option_descriptions, and pass it arguments for the $element and the $form_state that we can make alterations on. Some of you might have noticed the #options_descriptions key, which isn't in the FAPI documentation. We'll get to that shortly, but first let's look at our callback function.

Custom #after_build callback with #options_descriptions

All functions defined in #after_build are expected to return the $element argument after we've made our changes. As mentioned earlier, #options_descriptions isn't a standard FAPI option. Instead, it's a custom convention we'll use that our callback function will expect. Each option in the #options array shares a key with the items in the #options_descriptions array. That shared key is the glue between each option and its description. Writing our custom callback to take advantage of this, we end up with:

 * Provide a description to each option in the element
function _option_descriptions($element, &$form_state) {
  foreach (element_children($element) as $key) {
    $element[$key]['#description'] = t('!description', array(
      '!description' => $element['#options_descriptions'][$key],
  return $element;

While we only needed a couple lines of code, let's look into what they do. First we're looping over the results of our $element passed into Drupal's element_children() function. This looks over our $element array for any children, or any key that doesn't start with # (signifying that it's renderable). In the case of our checkboxes, this becomes each individual checkbox. This function returns their keys as an array that we can then loop over.

Next we alter the render array for each checkbox, adding a #description to it and assigning it the description with the shared key in our #options_descriptions array. Our final checkboxes now look like this:

Radio buttons with individual descriptions

Since each input is a renderable item, and we're leveraging Drupal's #description element for them, everything gets passed through the same theme hooks you'd expect for Drupal forms. These descriptions can get templated, preprocessed, or altered as you would anything else.


This example focused on checkboxes, however, this code works the same for groups of radio buttons as well. It's worth mentioning that this seems to be something that already works in Drupal 8, although it isn't well documented yet.

Code Drupal Drupal Planet

Read This Next