Skip to content

Form States System (#states)

When to Use

Use #states for client-side show/hide and enable/disable. Use AJAX when server-side logic or dynamic options needed.

Purpose: Client-side conditional field behavior (JavaScript)

Use When: Show/hide, enable/disable based on other field values

Alternative: AJAX (when server-side logic needed)

Reference:

Reference: Supported States

Visibility States:

State Effect
visible Show element when condition true
invisible Hide element when condition true

Interaction States:

State Effect
enabled Enable input when condition true
disabled Disable input when condition true
readonly Make read-only when condition true

Validation States:

State Effect
required Mark required when condition true
optional Mark optional when condition true

Special States:

State Effect Element Type
checked Check checkbox when condition true Checkbox
unchecked Uncheck checkbox when condition true Checkbox
expanded Expand when condition true Details
collapsed Collapse when condition true Details

Reference: Trigger Conditions

Checkbox Conditions:

Condition When True
['checked' => TRUE] Checkbox is checked
['unchecked' => TRUE] Checkbox is not checked

Value Conditions:

Condition When True
['value' => 'foo'] Exact value match
['value' => ['foo', 'bar']] Value in array
['!value' => 'foo'] Value NOT equal

State Conditions:

Condition When True
['empty' => TRUE] Field is empty
['filled' => TRUE] Field has value

Pattern: Basic Syntax

Single Condition:

'#states' => [
  'visible' => [
    ':input[name="enable_feature"]' => ['checked' => TRUE],
  ],
],

Multiple States (Same Element):

'#states' => [
  'visible' => [
    ':input[name="enable_feature"]' => ['checked' => TRUE],
  ],
  'required' => [
    ':input[name="enable_feature"]' => ['checked' => TRUE],
  ],
],

Reference: Selector Syntax

Input Name:

':input[name="field_name"]'           // Simple field
':input[name="container[field]"]'     // Nested field
':input[name="field[0][value]"]'      // Field API widget

Radio Buttons:

':input[name="field_name"]' => ['value' => 'option1']

Select Dropdowns:

'select[name="field_name"]' => ['value' => 'option1']

Checkboxes (Group):

':input[name="field_name[option1]"]' => ['checked' => TRUE]

Pattern: Complex Conditions

AND Conditions (All Must Be True):

'#states' => [
  'visible' => [
    ':input[name="enable_feature"]' => ['checked' => TRUE],
    ':input[name="user_role"]' => ['value' => 'admin'],
  ],
],

OR Conditions (Any Can Be True):

'#states' => [
  'visible' => [
    [':input[name="type"]' => ['value' => 'custom']],
    'or',
    [':input[name="type"]' => ['value' => 'advanced']],
  ],
],

XOR Conditions (Exactly One True):

'#states' => [
  'visible' => [
    [':input[name="option1"]' => ['checked' => TRUE]],
    'xor',
    [':input[name="option2"]' => ['checked' => TRUE]],
  ],
],

Nested Logic (AND + OR):

'#states' => [
  'visible' => [
    [':input[name="enable"]' => ['checked' => TRUE]],
    'and',
    [
      [':input[name="type"]' => ['value' => 'custom']],
      'or',
      [':input[name="type"]' => ['value' => 'advanced']],
    ],
  ],
],

Pattern: Practical Examples

Show Field When Checkbox Checked:

$form['custom_value'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Custom Value'),
  '#states' => [
    'visible' => [
      ':input[name="use_custom"]' => ['checked' => TRUE],
    ],
    'required' => [
      ':input[name="use_custom"]' => ['checked' => TRUE],
    ],
  ],
];

Show Field Based on Select:

$form['other_value'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Other'),
  '#states' => [
    'visible' => [
      'select[name="type"]' => ['value' => 'other'],
    ],
  ],
];

Disable Field When Another Empty:

$form['dependent'] = [
  '#type' => 'textfield',
  '#states' => [
    'disabled' => [
      ':input[name="primary"]' => ['empty' => TRUE],
    ],
  ],
];

Decision: States vs AJAX

Use #states When:

  • Simple show/hide logic
  • Field enable/disable
  • Required status changes
  • No server-side processing needed
  • Performance important (client-side only)

Use AJAX When:

  • Options must change (e.g., dependent dropdowns)
  • Need server-side data
  • Complex validation logic
  • Field structure must change
  • Need to load entities/data

Combine Both:

#states for instant UI feedback
AJAX for data loading
Best user experience

Common Mistakes

Wrong Selector:

// WRONG - missing :input
'name="field"' => ['checked' => TRUE]

// CORRECT
':input[name="field"]' => ['checked' => TRUE]

Nested Field Selector:

// WRONG - missing brackets
':input[name="container.field"]'

// CORRECT
':input[name="container[field]"]'

OR Logic:

// WRONG - separate conditions = AND
'visible' => [
  ':input[name="a"]' => ['checked' => TRUE],
  ':input[name="b"]' => ['checked' => TRUE],
]

// CORRECT - array + 'or'
'visible' => [
  [':input[name="a"]' => ['checked' => TRUE]],
  'or',
  [':input[name="b"]' => ['checked' => TRUE]],
]

See Also