States (Form) system

The last thing we are going to look at in this chapter is the States system of the Form API (not to be confused with the State API we covered in Chapter 6, Data Modeling and Storage). This allows us to define our form elements to behave somewhat dynamically based on the user interaction with the form. It doesn't use Ajax but relies on JavaScript to handle the manipulations. This is another great example of client-side behavior where we don't have to write a single line of JavaScript. So, let's see what this is.

The #states are simple properties we can add to form elements, which have the role of changing them depending on the state of other elements. The best way to understand this is through some examples. Imagine these two form elements:

$form['kids'] = [ 
  '#type' => 'checkbox', 
  '#title' => $this->t('Do you have kids?'), 
]; 
 
$form['kid_number'] = [ 
  '#type' => 'textfield', 
  '#title' => $this->t('How many kids do you have?'), 
]; 

In the first, we ask the user if they have kids (using a simple checkbox), while in the secondĀ ,we ask them how many kids they have. But why should the user actually see the second element if they don't have kids? This is where the #states property comes into play, and its role is to manipulate an element depending on the state of another. So instead, we can have this:

$form['kid_number'] = [ 
  '#type' => 'textfield', 
  '#title' => $this->t('How many kids do you have?'), 
  '#states' => [ 
    'visible' => [ 
      'input[name="kids"]' => ['checked' => TRUE], 
    ], 
  ], 
]; 

Now, the element for specifying the number of kids is only going to be visible if the state of the kid element is checked.

The #states property is an array whose key is the actual state that needs to be applied to the current element if the conditions inside are met. And the conditions can vary, but they all depend on a CSS selector (in our case input[name="kids"] matching another element).

Our example can also be written with this reverse logic:

'#states' => array( 
  'invisible' => array( 
    'input[name="kids"]' => array('checked' => FALSE), 
  ), 
),  

Apart from visible and invisible, the following states can be also applied to form elements: enabled, disabled, required, optional, checked, unchecked, expanded and collapsed. As for the conditions that can "trigger" these states, we can have the following (apart from checked, which we already saw): empty, filled, unchecked, expanded, collapsed and value.

So, for example, we can even control the state of an element depending on the value the user selected on another. Combining these possibilities can greatly improve our forms when it comes to user experience, decluttering, and even building logical form trees.