The dietary-restrictions field from earlier in this section is optional right up until it becomes essential: once a visitor says they need special accommodation, leaving that field blank isn't a minor omission anymore — it's a gap that could genuinely affect their stay. The required state lets a field's required-ness track another field's value, and it's the one #states behavior that reaches all the way into Drupal's validation system, not just the visual layer.
What you'll learn in this lesson
- How
#states => ['required' => ...]differs from every other state you've met so far - Why this is the one case where
#statesgenuinely does affect server-side validation, not just appearance - How the visible asterisk and the enforced validation stay in sync automatically
- A common pairing: making a field required at the same moment it becomes visible
The source file
Path (relative to the Examples module's root): modules/form_api_example/src/Form/StateDemo.php — extended here to make the dietary-restrictions field conditionally required.
$form['accommodation']['diet'] = [
'#type' => 'textfield',
'#title' => $this->t('Dietary Restrictions'),
'#states' => [
'required' => [
':input[name="needs_accommodation"]' => ['checked' => TRUE],
],
],
];
How it works
The one state that isn't purely cosmetic
'#states' => [
'required' => [
':input[name="needs_accommodation"]' => ['checked' => TRUE],
],
],
Every other state you've met — visible, invisible, disabled — only changes what happens in the browser. required is different: Drupal's Form API actually checks this condition again on the server during validation, not just in the client-side JavaScript. That means a visitor can't simply disable JavaScript and submit the form with the dietary field left blank while needs_accommodation is checked — the server independently re-evaluates the same condition and rejects the submission if it's not met. This is the one #states behavior that genuinely doubles as both a UX affordance and a real server-side validation rule, with zero extra code from you.
The asterisk and the enforcement move together
Watch the field's label closely in the browser: the moment needs_accommodation gets checked, a red required-field asterisk appears next to "Dietary Restrictions," in perfect sync with the server-side rule taking effect. You don't add that asterisk yourself — Drupal's render system draws it automatically for any element whose current required-state (static or dynamic) evaluates to true at render or re-render time.
Pairing required with invisible
$form['accommodation']['diet'] = [
'#type' => 'textfield',
'#title' => $this->t('Dietary Restrictions'),
'#states' => [
'required' => [
':input[name="needs_accommodation"]' => ['checked' => TRUE],
],
],
];
// The parent container from earlier lessons still controls visibility.
$form['accommodation']['#states'] = [
'invisible' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
];
A field that's required but still hidden would be a genuinely confusing dead end for a visitor — they'd get a validation error for a field they can't even see. That's why these two states are almost always paired on the same condition: the field becomes visible and required at exactly the same moment, so the two behaviors always agree with each other.
required whenever a checkbox is checked, but forget to also make it not required when the checkbox is unchecked, you can end up with a field that stays required even after the visitor unchecks the box — because your condition only ever described the "on" case. In practice this usually isn't a problem, since #states conditions are evaluated continuously and the required state simply stops applying once the condition (checked => TRUE) no longer matches — but it's worth testing both directions explicitly rather than assuming.See it for yourself
Visit /examples/form-api-example/state-demo on your own DDEV site. Check "Need Special Accommodations?", leave the Dietary Restrictions field blank, and click Submit — Drupal blocks the submission with a validation error, exactly as if you'd written #required => TRUE by hand.
Quick check: why does
requiredget real server-side enforcement whilevisibleanddisableddon't? (Answer: because "was this field required" is a rule Drupal's core validation logic already needs to check for every field regardless of#states— so extending that same check to also look at the dynamic condition was a natural, low-cost addition. Visibility and interactivity, by contrast, are purely presentational concerns that core's validation pipeline has no reason to care about.)
Key takeaways
#states => ['required' => ...]is the one state that's enforced server-side during validation, not just applied as a client-side visual cue.- The required-field asterisk and the actual validation rule stay in sync automatically — you never manage them separately.
- Pair a conditionally-required field's condition with the same condition used to make it visible, so a visitor is never asked to fill in a field they can't see.
- Test both directions of the condition (on and off) rather than assuming the "off" case is handled correctly by default.
Coming up next
You now know all three individual states — visible, disabled, required. The final lesson in this section shows you how to combine multiple conditions on a single state using and, or, and xor logic, for cases where one condition alone isn't specific enough.