Not every conditional field should disappear — sometimes you want the visitor to see the field exists, understand what it's for, but not be able to touch it until they've done something else first. Think of a "confirm password" field that stays visible but greyed out until a "new password" field has something typed into it. That's what the disabled state is for, and it behaves in a genuinely different way from hiding a field.
What you'll learn in this lesson
- How the
disabledstate works, and how it differs visually and functionally frominvisible - Why a disabled field's value is never submitted, and how that affects your
submitForm()code - How to combine
disabledwith a default value so the field shows something sensible while inactive - A subtlety around
#disabled(the static PHP property) versus#states => ['disabled' => ...](the dynamic one)
The source file
Path (relative to the Examples module's root): modules/form_api_example/src/Form/StateDemo.php — extended here with a disabled-state example built on the same accommodation form pattern.
$form['needs_accommodation'] = [
'#type' => 'checkbox',
'#title' => 'Need Special Accommodations?',
];
$form['room_preference'] = [
'#type' => 'select',
'#title' => $this->t('Preferred Room Location'),
'#options' => [
'ground_floor' => $this->t('Ground floor'),
'near_elevator' => $this->t('Near elevator'),
],
'#states' => [
'disabled' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
],
];
How it works
disabled — greyed out, but still visible
'#states' => [
'disabled' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
],
Same three-part shape as every other state you've seen: state, selector, condition. The difference is entirely in what the state does. Instead of a CSS visibility toggle, Drupal adds the browser's native disabled HTML attribute to the element. The field stays exactly where it is in the layout, visually greyed out by the browser's default styling, and — critically — genuinely unusable. A visitor can't type into it, select an option, or tab into it with the keyboard.
Disabled fields never reach submitForm()
This is the detail that catches people off guard. Browsers never submit the value of a form field carrying the disabled attribute — that's standard HTML behavior, nothing Drupal-specific about it. So if room_preference is disabled at the moment of submission, $form_state->getValue('room_preference') simply won't have a value from that request, regardless of what was pre-filled or previously selected. If your submit handler needs a fallback, use #default_value to make sure there's something sensible on the PHP side even when the browser sends nothing.
Static #disabled vs. dynamic #states => ['disabled' => ...]
// Static: always disabled, no matter what. Set once, in PHP, at render time.
$form['legacy_field']['#disabled'] = TRUE;
// Dynamic: disabled state changes live, based on another field's current value.
$form['room_preference']['#states'] = [
'disabled' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
];
Both end up producing a disabled field in the browser, but they solve different problems. #disabled is a plain boolean property you'd set for a field that's always off-limits — read-only display data, for instance. #states's disabled key is for a field whose disabled-ness genuinely depends on something the visitor does, live, without a page reload. Reach for the simpler #disabled first; only add #states when the condition actually needs to react to user interaction.
validateForm() before trusting the submitted value.See it for yourself
Visit /examples/form-api-example/state-demo on your own DDEV site. Before checking "Need Special Accommodations?", try clicking into the room-preference dropdown — it won't respond. Check the box, and it becomes immediately clickable.
Quick check: you disable a select field via
#states, and its#default_valueis set to'ground_floor'. The visitor never enables it and submits the form as-is. What value does$form_state->getValue()return for that field? (Answer: nothing meaningful from the submission itself — disabled fields don't get submitted by the browser at all. Whatever your submit handler does with a missing value — falling back to the default, treating it as unset — is entirely up to your own code, since Drupal won't supply one for you.)
Key takeaways
#states => ['disabled' => ...]greys out a field and adds the real HTMLdisabledattribute — the field stays visible but becomes unusable.- Browsers never submit the value of a disabled field; plan your
submitForm()logic around that fact, using#default_valueas a PHP-side fallback where needed. - Use the static
#disabledproperty for fields that are always off-limits; use#states'sdisabledkey only when the condition needs to change live based on another field. - Like every other
#statesbehavior, disabling is a client-side convenience — re-validate any business-critical condition server-side too.
Coming up next
You've now covered visibility and interactivity. The next lesson covers the third major #states behavior: making a field conditionally required — and why that one interacts with Drupal's validation system in a way the other two don't.