How Drupal renders and themes validation error messagesfor Drupal 8 , 9 , 10 , and 11

Last updated :  

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.twig template 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.

Don't remove the top-of-page message summary just because you've styled inline errors. Screen reader users navigating a long form may never reach the specific field visually, but the summary — especially with 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 a MessengerInterface::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 a MessengerInterface::TYPE_* constant instead.
  • $form_state->setErrorByName() automatically produces a TYPE_ERROR message — you don't call the Messenger service manually for ordinary field validation failures.
  • Every message renders through status-messages.html.twig, grouped by type into messages--status/messages--warning/messages--error classes you can style independently.
  • An individually-errored field gets an automatic error class on its form-item wrapper — 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.