A validation error is only useful if the person filling out the form actually notices it. Drupal renders a failed validation in two places at once — a summary message near the top of the page, and an inline marker right next to the specific field that failed — and both of those render paths are themeable, independently of each other. This lesson covers styling both.
What you'll learn in this lesson
- How to add a message the modern way, via the Messenger service (not the removed
drupal_set_message()) - How the
status-messages.html.twigtemplate renders status/warning/error messages with distinct classes - How Drupal marks an individual errored field so you can style it inline
- The accessibility reason both the summary and the inline marker matter
Adding a message the current way
Older Drupal tutorials often reference drupal_set_message() — that function was deprecated in Drupal 8.5 and removed entirely by Drupal 9. If you see it in any code sample, treat it as a signal the sample is out of date. The real, current API is the Messenger service:
\Drupal::messenger()->addMessage(
$this->t('Something went wrong with your submission.'),
\Drupal\Core\Messenger\MessengerInterface::TYPE_ERROR
);
Form validation errors set through $form_state->setErrorByName('field_name', $message) are automatically converted into TYPE_ERROR messages by the Form API itself — you don't call the Messenger service directly for ordinary validation failures, only for status/warning/error messages you want to show outside of form validation.
Theming the message summary
Every message — regardless of how it was added — renders through the same status-messages.html.twig template, which loops over messages grouped by type and applies a distinct class to each group:
{# themes/custom/mytheme/templates/status-messages.html.twig #}
{% for type, messages in message_list %}
<div class="messages messages--{{ type }}" role="{{ type == 'error' ? 'alert' : 'status' }}">
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
Because the template classes each group as messages--status, messages--warning, or messages--error, your theme's CSS can style each severity level distinctly (a red border for errors, a muted gray for status) without any PHP changes — this is a pure Twig/CSS override, exactly like the element-level overrides from earlier lessons.
Styling the inline field error
The message summary tells someone that something failed; it doesn't point at which field. Drupal handles that separately: when setErrorByName() flags a field, the rendered form-item wrapper for that specific element gets an error class automatically, which you can target directly:
.form-item.error input,
.form-item.error select,
.form-item.error textarea {
border-color: #e53e3e;
background-color: #fff5f5;
}
No template override is needed for this part — the class is already there in the default markup; you're only adding CSS that reacts to it.
role="alert" as shown above — gets announced immediately. The two mechanisms serve different audiences; keep both.Quick check: you're reviewing an old code sample that calls
drupal_set_message()directly. Is this code safe to copy into a Drupal 11 module? No — that function was removed in Drupal 9. The direct replacement is\Drupal::messenger()->addMessage(), with aMessengerInterface::TYPE_*constant in place of the old string-based severity argument.
Key takeaways
drupal_set_message()is dead code, removed in Drupal 9 — use\Drupal::messenger()->addMessage()with aMessengerInterface::TYPE_*constant instead.$form_state->setErrorByName()automatically produces aTYPE_ERRORmessage — you don't call the Messenger service manually for ordinary field validation failures.- Every message renders through
status-messages.html.twig, grouped by type intomessages--status/messages--warning/messages--errorclasses you can style independently. - An individually-errored field gets an automatic
errorclass on itsform-itemwrapper — style it directly with CSS, no template override needed. - Keep both the summary message and the inline field marker — they serve different accessibility needs, especially for screen reader users.
Coming up next
Sometimes the right place for a form isn't a dedicated page at all — it's embedded inside a sidebar block. The next lesson covers exactly that: rendering a form programmatically from within a custom block plugin.