diff --git a/core/core.services.yml b/core/core.services.yml
index 064a13a..c9bdbaa 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -395,6 +395,7 @@ services:
     arguments: ['@request_stack', '@url_generator']
   form_error_handler:
     class: Drupal\Core\Form\FormErrorHandler
+    arguments: ['@renderer']
   form_cache:
     class: Drupal\Core\Form\FormCache
     arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy']
diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php
index 695f023..9aa5440 100644
--- a/core/lib/Drupal/Core/Form/FormErrorHandler.php
+++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\RendererInterface;
 
 /**
  * Handles form errors.
@@ -14,6 +15,34 @@ class FormErrorHandler implements FormErrorHandlerInterface {
   use MessengerTrait;
 
   /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a FormErrorHandler instance.
+   *
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   */
+  public function __construct(RendererInterface $renderer) {
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    // Instantiates this form class.
+    return new static(
+      // Load the service required to construct this class.
+      $container->get('renderer')
+    );
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function handleFormErrors(array &$form, FormStateInterface $form_state) {
@@ -39,10 +68,34 @@ public function handleFormErrors(array &$form, FormStateInterface $form_state) {
    */
   protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
     $errors = $form_state->getErrors();
-
-    // Loop through all form errors and set an error message.
-    foreach ($errors as $error) {
-      $this->messenger()->addMessage($error, 'error');
+    $items = [
+      '#theme' => 'item_list',
+      '#items' => [],
+      '#list_type' => 'ul',
+    ];
+    // Loop through all form errors and set an id.
+    foreach ($errors as $name => $error) {
+      $form_element = FormElementHelper::getElementByName($name, $form);
+      $has_id = !empty($form_element['#id']);
+      if ($has_id) {
+        $items['#wrapper_attributes'] = [
+          'id' => $form_element['#id'] . '--error-message',
+        ];
+      }
+      $message = [
+        'message' => [
+          '#markup' => $error,
+        ],
+        'items' => $items,
+      ];
+      if ($has_id) {
+        // Render the error messages as HTML.
+        $message = $this->renderer->renderPlain($message);
+        $this->messenger()->addMessage($message, 'error');
+      }
+      else {
+        $this->messenger()->addMessage($error, 'error');
+      }
     }
   }
 
@@ -162,6 +215,11 @@ protected function setElementErrorsFromFormState(array &$form, FormStateInterfac
 
     // Store the errors for this element on the element directly.
     $elements['#errors'] = $form_state->getError($elements);
+
+    // Add aria-describedby attribute to the form element.
+    if (($elements['#errors']) !== NULL) {
+      $elements['#attributes']['aria-describedby'] = $elements['#id'] . '--status-message';
+    }
   }
 
 }
diff --git a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
index 5aff409..c8d4898 100644
--- a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
+++ b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
@@ -190,8 +190,11 @@ public function testErrorMessagesNotInline() {
       ->method('addMessage')
       ->with('this missing element is invalid', 'error', FALSE);
 
-    $this->renderer->expects($this->never())
-      ->method('renderPlain');
+    $this->renderer->expects($this->any())
+      ->method('renderPlain')
+      ->will($this->returnCallback(function ($message) {
+        return $message['message']['#markup'];
+      }));
 
     $this->testForm['#disable_inline_form_errors'] = TRUE;
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
index 3ba570d..9a702df 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\Render\RendererInterface;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -27,6 +28,13 @@ class FormErrorHandlerTest extends UnitTestCase {
   protected $messenger;
 
   /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $renderer;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -34,7 +42,10 @@ protected function setUp() {
 
     $this->messenger = $this->createMock(MessengerInterface::class);
 
+    $this->renderer = $this->createMock(RendererInterface::class);
+
     $this->formErrorHandler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
+      ->setConstructorArgs([$this->renderer])
       ->setMethods(['messenger'])
       ->getMock();
 
@@ -67,6 +78,12 @@ public function testDisplayErrorMessages() {
       ->method('addMessage')
       ->with('this missing element is invalid', 'error');
 
+    $this->renderer->expects($this->any())
+      ->method('renderPlain')
+      ->will($this->returnCallback(function ($message) {
+        return $message['message']['#markup'];
+      }));
+
     $form = [
       '#parents' => [],
       '#array_parents' => [],
