Conditional fields in Drupal 7 with #states

Have you ever had to create a conditional input field? For example, one field is a checkbox that reads "Do you have children?" and if the user selects "Yes", we want to show an additional input field that asks "How many?" (and if the user selects "No" the field disappears).

I ran into this in a recent project and, as usual, added some custom javascript using #after_build that did exactly that. I'm pretty sure this is a widespread practice.

Don't do it!

Despite not being techically wrong, the aforementioned solution is, as of Drupal 7, outdated. Changes in the Form API have introduced the same functionality natively, which is a lot easier to implement and requires zero custom javascript!

To accomplish this, we use #states.

Just so we're on the same page here, this is what our form looked like before:

$form['haskids'] = array(
  '#type' => 'checkbox',
  '#title' => t('Do you have children?'),
);

$form['numkids'] = array(
  '#type' => 'textfield',
  '#title' => t('How many?'),
);

The secret is to use the new "container" type to wrap our conditional elements and control their visibility through the #states attribute:

$form['haskids'] = array(
  '#type' => 'checkbox',
  '#title' => t('Do you have children?'),
);

$form['kids_container'] = array(
  '#type' => 'container',
  '#states' => array(
    'invisible' => array(
      'input[name="haskids"]' => array('checked' => FALSE),
    ),
  ),
);

$form['kids_container']['numkids'] = array(
  '#type' => 'textfield',
  '#title' => t('How many?'),
);

Explanation

Using #states, we defined an invisible state for the container, which is triggered when the value of the "checked" attribute of the input specified by the selector is FALSE. It might at first seem a little confusing to mix in jQuery selector in PHP, but this is just how Drupal rolls and manages to bind the javascript event automatically. If in dobut, simply replace name="haskids" with your field's "name" attribute!

Note: The container isn't necessary, any element can be toggled with states individually too, although it can be useful to manipulate multiple fields at once.

To learn more about states and how to use them, read about drupal_process_states().

Done!

That was easy, right? It took absolutely no javascript and no #after_build callbacks!

Hope you enjoyed this and catch you next time!