diff --git a/core/modules/inline_form_errors/inline_form_errors.module b/core/modules/inline_form_errors/inline_form_errors.module
index 52e572d300..3574094296 100644
--- a/core/modules/inline_form_errors/inline_form_errors.module
+++ b/core/modules/inline_form_errors/inline_form_errors.module
@@ -5,6 +5,8 @@
  * Enables inline form errors.
  */
 
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\FormElementInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
@@ -49,6 +51,43 @@ function inline_form_errors_preprocess_datetime_wrapper(&$variables) {
 }
 
 /**
+ * Implements hook_element_info_alter().
+ */
+function inline_form_errors_element_info_alter(array &$info) {
+  // Add a processor for form elements and pseudo form elements.
+  $determiner = \Drupal::service('inline_form_errors.form_element_determiner');
+  foreach ($info as $element_type => $element_info) {
+    if ($determiner->isFormElement($element_type)) {
+      $info[$element_type]['#process'][] = 'inline_form_errors_process_form_element';
+    }
+  }
+}
+
+/**
+ * Process all form elements and pseudo form elements.
+ *
+ * @param array $element
+ *   An associative array containing the properties and children of the
+ *   element. Note that $element must be taken by reference here, so processed
+ *   child elements are taken over into $form_state.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form.
+ * @param array $complete_form
+ *   The complete form structure.
+ *
+ * @return array
+ *   The processed element.
+ */
+function inline_form_errors_process_form_element(array &$element, FormStateInterface $form_state, array &$complete_form) {
+  // Prevent displaying inline form errors when disabled for the whole form.
+  if (!empty($complete_form['#disable_inline_form_errors'])) {
+    $element['#error_no_message'] = TRUE;
+  }
+
+  return $element;
+}
+
+/**
  * Populates form errors in the template.
  */
 function _inline_form_errors_set_errors(&$variables) {
diff --git a/core/modules/inline_form_errors/inline_form_errors.services.yml b/core/modules/inline_form_errors/inline_form_errors.services.yml
new file mode 100644
index 0000000000..5114bdc104
--- /dev/null
+++ b/core/modules/inline_form_errors/inline_form_errors.services.yml
@@ -0,0 +1,4 @@
+services:
+  inline_form_errors.form_element_determiner:
+    class: Drupal\inline_form_errors\FormElementDeterminer
+    arguments: ['@plugin.manager.element_info']
diff --git a/core/modules/inline_form_errors/src/FormElementDeterminer.php b/core/modules/inline_form_errors/src/FormElementDeterminer.php
new file mode 100644
index 0000000000..8b13517696
--- /dev/null
+++ b/core/modules/inline_form_errors/src/FormElementDeterminer.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\inline_form_errors;
+
+use Drupal\Core\Render\Element\FormElementInterface;
+use Drupal\Core\Render\ElementInfoManager;
+
+/**
+ * Provides a way to determine if a render array is for a form element.
+ */
+class FormElementDeterminer {
+
+  /**
+   * A list of pseudo form elements.
+   *
+   * Define a list of elements that are not form elements but are primarily
+   * used in forms and supported by Inline Form Errors.
+   *
+   * @var array
+   */
+  protected $pseudoFormElements = [
+    'details',
+    'fieldset',
+    'datetime_wrapper',
+  ];
+
+  /**
+   * The element info manager.
+   *
+   * @var \Drupal\Core\Render\ElementInfoManager
+   */
+  protected $elementInfoManager;
+
+  /**
+   * FormElementDeterminer constructor.
+   *
+   * @param \Drupal\Core\Render\ElementInfoManager $element_info_manager
+   *   The element info manager.
+   */
+  public function __construct(ElementInfoManager $element_info_manager) {
+    $this->elementInfoManager = $element_info_manager;
+  }
+
+  /**
+   * Determine if a render element type also is a form element.
+   *
+   * @param string $element_type
+   *   The element type.
+   *
+   * @return bool
+   *   TRUE if the given element type is a form element or FALSE otherwise.
+   */
+  public function isFormElement($element_type) {
+    return in_array($element_type, $this->pseudoFormElements) || $this->elementInstanceOfFormElementInterface($element_type);
+  }
+
+  /**
+   * Determine if a render element type implements FormElementInterface.
+   *
+   * @param string $element_type
+   *   The element type.
+   *
+   * @return bool
+   *   TRUE if the given element type implements FormElementInterface or FALSE
+   *   otherwise.
+   */
+  protected function elementInstanceOfFormElementInterface($element_type) {
+    $element_definitions = $this->elementInfoManager->getDefinitions();
+
+    if (!empty($element_definitions[$element_type])) {
+      $interfaces = class_implements($element_definitions[$element_type]['class']);
+      return isset($interfaces['Drupal\Core\Render\Element\FormElementInterface']);
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/inline_form_errors/src/FormErrorHandler.php b/core/modules/inline_form_errors/src/FormErrorHandler.php
index 2b892e99dd..bfe6dbd05d 100644
--- a/core/modules/inline_form_errors/src/FormErrorHandler.php
+++ b/core/modules/inline_form_errors/src/FormErrorHandler.php
@@ -47,15 +47,23 @@ public function __construct(TranslationInterface $string_translation, LinkGenera
   /**
    * Loops through and displays all form errors.
    *
+   * To disable inline form errors for an entire form set the
+   * #disable_inline_form_errors property to TRUE on the top level of the $form
+   * array, ie:
+   * @code
+   * $form['#disable_inline_form_errors property'] = TRUE;
+   * @endcode
+   * This should only be done when another appropriate accessibility strategy is
+   * in place.
+   *
    * @param array $form
    *   An associative array containing the structure of the form.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    */
   protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
-    // Use the original error display for Quick Edit forms, because in this case
-    // the errors are already near the form element.
-    if ($form['#form_id'] === 'quickedit_field_form') {
+    // Skip generating inline form errors when opted out.
+    if (!empty($form['#disable_inline_form_errors'])) {
       parent::displayErrorMessages($form, $form_state);
       return;
     }
diff --git a/core/modules/inline_form_errors/tests/src/Kernel/FormElementInlineErrorTest.php b/core/modules/inline_form_errors/tests/src/Kernel/FormElementInlineErrorTest.php
new file mode 100644
index 0000000000..9bb247eb75
--- /dev/null
+++ b/core/modules/inline_form_errors/tests/src/Kernel/FormElementInlineErrorTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Tests\inline_form_errors\Kernel;
+
+use Drupal\Core\Form\FormState;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests messages on form elements.
+ *
+ * @group InlineFormErrors
+ */
+class FormElementInlineErrorTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['inline_form_errors'];
+
+  /**
+   * Test that form elements get the #error_no_message property when opting out
+   * using #disable_inline_form_errors on the form's top level.
+   */
+  public function testDisplayErrorMessagesNotInline() {
+    $form_id = 'test';
+
+    $form = [
+      '#parents' => [],
+      '#disable_inline_form_errors' => TRUE,
+      '#array_parents' => [],
+    ];
+    $form['test'] = [
+      '#type' => 'textfield',
+      '#title' => 'Test',
+      '#parents' => ['test'],
+      '#id' => 'edit-test',
+      '#array_parents' => ['test'],
+    ];
+    $form_state = new FormState();
+
+    \Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
+    \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
+    \Drupal::formBuilder()->doBuildForm($form_id, $form, $form_state);
+
+    // Just test if the #error_no_message property is TRUE. FormErrorHandlerTest
+    // tests if the property actually hides the error message.
+    $this->assertArraySubset(['#error_no_message' => TRUE], $form['test']);
+  }
+
+}
diff --git a/core/modules/inline_form_errors/tests/src/Unit/FormElementDeterminerTest.php b/core/modules/inline_form_errors/tests/src/Unit/FormElementDeterminerTest.php
new file mode 100644
index 0000000000..9874b07210
--- /dev/null
+++ b/core/modules/inline_form_errors/tests/src/Unit/FormElementDeterminerTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Tests\inline_form_errors\Unit;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\FormElementInterface;
+use Drupal\Core\Render\ElementInfoManager;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\inline_form_errors\FormElementDeterminer;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\inline_form_errors\FormElementDeterminer
+ * @group InlineFormErrors
+ */
+class FormElementDeterminerTest extends UnitTestCase {
+
+  /**
+   * @covers ::isFormElement
+   * @covers ::elementInstanceOfFormElementInterface
+   */
+  public function testIsFormElement() {
+    $element_info_manager = $this->getMockBuilder(ElementInfoManager::class)
+      ->setConstructorArgs([
+        $this->getMock(\Traversable::class),
+        $this->getMock(CacheBackendInterface::class),
+        $this->getMock(CacheTagsInvalidatorInterface::class),
+        $this->getMock(ModuleHandlerInterface::class),
+        $this->getMock(ThemeManagerInterface::class),
+      ])
+      ->setMethods(['getDefinitions', 'createInstance'])
+      ->getMock();
+
+    $element_info_manager->expects($this->any())
+      ->method('getDefinitions')
+      ->willReturn([
+        'form_element_type' => ['class' => '\Drupal\Core\Render\Element\FormElement'],
+        'element_type' => ['class' => '\Drupal\Core\Render\Element\RenderElement'],
+      ]);
+
+    $form_element_determiner = new FormElementDeterminer($element_info_manager);
+
+    // Test that an element implementing FormElementInterface is detected.
+    $this->assertTrue($form_element_determiner->isFormElement('form_element_type'));
+
+    // Test that an element implementing only ElementInterface returns FALSE.
+    $this->assertFalse($form_element_determiner->isFormElement('element_type'));
+
+    // Test that any element marked as supported by inline form errors is
+    // detected. The 'details' element is one of the types specified in
+    // \Drupal\inline_form_errors\FormElementDeterminer::$pseudoFormElements.
+    $this->assertTrue($form_element_determiner->isFormElement('details'));
+  }
+
+}
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 acd255ba27..5b42a8f7eb 100644
--- a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
+++ b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
@@ -140,12 +140,9 @@ public function testSetElementErrorsFromFormState() {
   }
 
   /**
-   * Test that Quick Edit forms show non-inline errors.
-   *
-   * @covers ::handleFormErrors
-   * @covers ::displayErrorMessages
+   * Test that opting out of inline form errors works.
    */
-  public function testDisplayErrorMessagesNotInlineQuickEdit() {
+  public function testDisplayErrorMessagesNotInline() {
     $form_error_handler = $this->getMockBuilder(FormErrorHandler::class)
       ->setConstructorArgs([$this->getStringTranslationStub(), $this->getMock(LinkGeneratorInterface::class), $this->getMock(RendererInterface::class)])
       ->setMethods(['drupalSetMessage'])
@@ -157,7 +154,7 @@ public function testDisplayErrorMessagesNotInlineQuickEdit() {
 
     $form = [
       '#parents' => [],
-      '#form_id' => 'quickedit_field_form',
+      '#disable_inline_form_errors' => TRUE,
       '#array_parents' => [],
     ];
     $form['test'] = [
@@ -165,7 +162,7 @@ public function testDisplayErrorMessagesNotInlineQuickEdit() {
       '#title' => 'Test',
       '#parents' => ['test'],
       '#id' => 'edit-test',
-      '#array_parents' => ['test']
+      '#array_parents' => ['test'],
     ];
     $form_state = new FormState();
     $form_state->setErrorByName('test', 'invalid');
diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
index ba2dac38eb..7603159eb9 100644
--- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php
+++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php
@@ -114,6 +114,10 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
       '#attributes' => ['class' => ['quickedit-form-submit']],
     ];
 
+    // Use the non-inline form error display for Quick Edit forms, because in
+    // this case the errors are already near the form element.
+    $form['#disable_inline_form_errors'] = TRUE;
+
     // Simplify it for optimal in-place use.
     $this->simplify($form, $form_state);
 
