Enable a button basing on multiple form values being equal I would not like users to be able to submit a form until: 


All fields are filled out
The password and confirm password fields are equal  


I can do this with JavaScript, potentially, but then the JavaScript code would managing some states and the PHP code would manage other states. It seems messy.

This is the code I wrote.

'disabled' =&gt; array(
// disable button if missing required fields
        array(
          ':input[name="field1"]' =&gt; array('value' =&gt; ''),
        ),
        array(
          ':input[name="field2"]' =&gt; array('value' =&gt; ''),
        ),
        array(
          ':input[name="password"]' =&gt; array('value' =&gt; ''),
        ),
        array(
          ':input[name="confirm_password"]' =&gt; array('value' =&gt; ''),
        ),
      ),

      // only enable the button when passwords are ==
'enabled' =&gt; array(
        array(
          ':input[name="password"]' =&gt; array('value' =&gt; ':input[name="confirm_password"]')
        )
      )
'array('value' =&gt; ':input[name="confirm_password"]')


This means the value equals the literal string ":input[name="confirm_password"]".
As far as I know, #states can't check if the value of two fields are the same.


  I can do this with JavaScript, potentially, but then the JavaScript
  code would managing some states and the PHP code would manage other
  states. It seems messy.


Here is a way that may seem less messy. Use a hidden compare_passwords field whose value will indicate whether the password and confirm_password fields have the same value:

$form['compare_passwords'] = array('#type' =&gt; 'hidden', '#default_value' =&gt; 'fail');


Use the value of that field in the #states array for your button.

':input[name="compare_passwords"]' =&gt; array('value' =&gt; 'match')


Write some JavaScript that changes the value of compare_passwords whenever the value of password or confirm_password changes (use .on() instead of bind() if your site uses jQuery 1.7+):

var password_selector = ':input[name="password"]';
var confirm_selector = ':input[name="confirm_password"]';
var compare_selector = ':input[name="compare_passwords"]';
$(password_selector + ',' + confirm_selector).bind("change paste keyup blur", function () {
  if ($(password_selector).val() == $(confirm_selector).val()) {
    $(compare_selector).val('match').change();
  } else {
    $(compare_selector).val('fail').change();
  }
});


By the way, you can use '!value' to put all your "does not equal empty string" conditions under "enabled", using the Boolean "AND" structure. The Boolean "OR" structure, which is wrapping each condition in its own array, is harder to understand if you aren't familiar with #states.

// Only enable the button when all fields have a value and passwords match.
// The value in "compare_passwords" is dynamically updated by my_script.js
//  whenever the user types inside the password or compare_password fields.
$form['my_button']['#states'] = array(
  'enabled' =&gt; array(
    ':input[name="field1"]' =&gt; array('!value' =&gt; ''),
    ':input[name="field2"]' =&gt; array('!value' =&gt; ''),
    ':input[name="password"]' =&gt; array('!value' =&gt; ''),
    ':input[name="confirm_password"]' =&gt; array('!value' =&gt; ''),
    ':input[name="compare_passwords"]' =&gt; array('value' =&gt; 'match'),
  ),
);