The last lesson used invisible to hide the dietary-restrictions field until it was needed. That solved the visible problem, but there's a second, less obvious way to "hide" something in Drupal — and picking the wrong one can leak data you meant to keep out of the page entirely. This lesson is about knowing exactly which one you're reaching for, and why.
What you'll learn in this lesson
- The real difference between the
visible/invisiblestate pair and thehiddenstate - Which one actually removes markup from the page, and which one just hides it visually
- How to invert the logic using
visibleinstead ofinvisiblefor the same result, written the other way around - A concrete rule for choosing between them based on what the field contains
The source file
Path (relative to the Examples module's root): modules/form_api_example/src/Form/StateDemo.php — the same accommodation form from the previous lesson, examined from a different angle.
$form['accommodation'] = [
'#type' => 'container',
'#attributes' => [
'class' => 'accommodation',
],
'#states' => [
'invisible' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
],
];
How it works
visible/invisible — a CSS toggle, markup stays in the DOM
This is what the example above uses. Under the hood, Drupal's drupal.states library toggles a CSS class that sets display: none. The HTML for the dietary field is still sitting in the page's source the whole time — a curious visitor could open browser dev tools and find it, hidden but present. For a field like "dietary restrictions," that's a completely reasonable trade-off: nothing sensitive, and toggling CSS is instant with no extra network request.
hidden — a separate state, not just a synonym
'#states' => [
'invisible' => [
':input[name="needs_accommodation"]' => ['checked' => FALSE],
],
],
It's tempting to assume hidden is just another spelling of invisible — it isn't. hidden is a distinct, valid state key recognized by drupal_process_states(), and while it behaves similarly in the browser (also a CSS-driven toggle, not a true removal from the DOM), the two are tracked as separate state names in Drupal's states system and can theoretically be combined or targeted independently by more advanced JavaScript. In practice, for the vast majority of real modules, visible/invisible is the pair you'll reach for — it's the one demonstrated throughout Drupal core itself.
Writing the same behavior the other direction
'#states' => [
'visible' => [
':input[name="needs_accommodation"]' => ['checked' => TRUE],
],
],
This produces the identical end result as the original invisible/FALSE version — the field shows only when the checkbox is checked. Which one you write is a matter of which reads more naturally for the behavior you're describing. Some developers prefer always stating the "shown" condition explicitly (visible + TRUE); others prefer describing the default-hidden case (invisible + FALSE). Both compile to the same client-side behavior — pick whichever your team finds easier to read at a glance.
#states at all. Build the element conditionally in PHP, inside an if statement in buildForm(), before it's ever added to the render array. #states is for "the user can see it exists but it's not relevant right now," not for genuine access control.See it for yourself
Visit /examples/form-api-example/state-demo again. Open your browser's dev tools, inspect the page while the dietary field is hidden, and you'll find its <input> tag sitting right there in the markup with a display: none style — proof that invisible is a presentation toggle, not a removal.
Quick check: a field holds a temporary internal reference code that should never reach an anonymous visitor's browser at all, not even hidden in the markup. Should you reach for
#states => ['invisible' => ...]? (Answer: no — sinceinvisibleonly hides via CSS, the value would still be sent to every visitor's browser regardless of whether the field is currently shown. Genuinely sensitive data needs to be excluded from the render array in PHP entirely, not toggled with#states.)
Key takeaways
visible/invisibleis the standard pair for showing and hiding non-sensitive fields — it toggles a CSS class, leaving the markup in the DOM.hiddenis a distinct, separately-tracked state name, not a synonym forinvisible— in practice, stick withvisible/invisibleunless you have a specific reason not to.- You can write the same interaction as either
invisible+FALSEorvisible+TRUE— both produce identical behavior; choose whichever reads more clearly. - Never use any
#statesvisibility toggle as a substitute for genuinely excluding sensitive data from the render array — the markup (and its value) is still delivered to the browser either way.
Coming up next
Hiding and showing fields covers one kind of conditional behavior. The next lesson covers a different one: using #states to enable and disable a field entirely — letting it stay visible, but temporarily unusable, based on another field's value.