diff --git a/core/lib/Drupal/Core/Render/Element/Details.php b/core/lib/Drupal/Core/Render/Element/Details.php
index 92bbb32bd1..c77493edc7 100644
--- a/core/lib/Drupal/Core/Render/Element/Details.php
+++ b/core/lib/Drupal/Core/Render/Element/Details.php
@@ -3,6 +3,7 @@
 namespace Drupal\Core\Render\Element;
 
 use Drupal\Core\Render\Element;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a render element for a details element, similar to a fieldset.
@@ -89,6 +90,12 @@ public static function preRenderDetails($element) {
       }
     }
 
+    // Indicate that there is a child element with an error.
+    if (!empty($element['#children_errors'])) {
+      $element['#attributes']['class'][] = 'details--child-error';
+      $element['#title'] = new TranslatableMarkup('@title <span class="visually-hidden child-error-indicator">(contains error)</span>', ['@title' => $element['#title']]);
+    }
+
     return $element;
   }
 
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index 5a26db0c00..0a82f285ae 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -204,6 +204,12 @@ public static function preRenderGroup($element) {
       }
     }
 
+    // Set attributes to indicate that there is a child element with an error.
+    if (!empty($element['#children_errors'])) {
+      $element['#attributes']['class'][] = 'form-wrapper--child-error';
+      $element['#attributes']['data-child-error-count'] = count($element['#children_errors']);
+    }
+
     return $element;
   }
 
diff --git a/core/misc/vertical-tabs.es6.js b/core/misc/vertical-tabs.es6.js
index 61ea218709..1cbbe4f4be 100644
--- a/core/misc/vertical-tabs.es6.js
+++ b/core/misc/vertical-tabs.es6.js
@@ -78,8 +78,9 @@
         $details.each(function () {
           const $that = $(this);
           const verticalTab = new Drupal.verticalTab({
-            title: $that.find('> summary').text(),
+            title: $that.find('> summary').html(),
             details: $that,
+            childErrorCount: $that.data('child-error-count'),
           });
           tabList.append(verticalTab.item);
           $that
@@ -126,6 +127,8 @@
    *   The name of the tab.
    * @param {jQuery} settings.details
    *   The jQuery object of the details element that is the tab pane.
+   * @param {number} settings.childErrorCount
+   *   The number of form errors contained inside the tab pane.
    *
    * @fires event:summaryUpdated
    *
@@ -259,6 +262,10 @@
    *   An object with the following keys:
    * @param {string} settings.title
    *   The name of the tab.
+   * @param {jQuery} settings.details
+   *   The jQuery object of the details element that is the tab pane.
+   * @param {number} settings.childErrorCount
+   *   The number of form errors contained inside the tab pane.
    *
    * @return {object}
    *   This function has to return an object with at least these keys:
@@ -271,10 +278,15 @@
     const tab = {};
     tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>')
       .append(tab.link = $('<a href="#"></a>')
-        .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title))
+        .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').html(settings.title))
         .append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>'),
         ),
       );
+
+    if (settings.childErrorCount) {
+      tab.item.addClass('vertical-tab__menu-item--child-error');
+    }
+
     return tab;
   };
 }(jQuery, Drupal, drupalSettings));
diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js
index 18270073cb..298a6846d0 100644
--- a/core/misc/vertical-tabs.js
+++ b/core/misc/vertical-tabs.js
@@ -39,8 +39,9 @@
         $details.each(function () {
           var $that = $(this);
           var verticalTab = new Drupal.verticalTab({
-            title: $that.find('> summary').text(),
-            details: $that
+            title: $that.find('> summary').html(),
+            details: $that,
+            childErrorCount: $that.data('child-error-count')
           });
           tabList.append(verticalTab.item);
           $that.removeClass('collapsed').attr('open', true).addClass('vertical-tabs__pane').data('verticalTab', verticalTab);
@@ -138,7 +139,12 @@
 
   Drupal.theme.verticalTab = function (settings) {
     var tab = {};
-    tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>').append(tab.link = $('<a href="#"></a>').append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title)).append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')));
+    tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>').append(tab.link = $('<a href="#"></a>').append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').html(settings.title)).append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')));
+
+    if (settings.childErrorCount) {
+      tab.item.addClass('vertical-tab__menu-item--child-error');
+    }
+
     return tab;
   };
 })(jQuery, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
index c15ca70bbe..d171227a8c 100644
--- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php
+++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
@@ -55,6 +55,7 @@ protected function setUp() {
    */
   public function testNodeEdit() {
     $this->drupalLogin($this->webUser);
+    $web_assert = $this->assertSession();
 
     $title_key = 'title[0][value]';
     $body_key = 'body[0][value]';
@@ -123,13 +124,16 @@ public function testNodeEdit() {
     // This invalid date will trigger an error.
     $edit['created[0][value][date]'] = $this->randomMachineName(8);
     // Get the current amount of open details elements.
-    $open_details_elements = count($this->cssSelect('details[open="open"]'));
+    $open_details_count = count($this->cssSelect('details[open="open"]'));
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    // The node author details must be open.
-    $this->assertRaw('<details class="node-form-author js-form-wrapper form-wrapper" data-drupal-selector="edit-author" id="edit-author" open="open">');
+    // The node author details must be open and have child error attributes.
+    $this->assertRaw('<details class="node-form-author js-form-wrapper form-wrapper details--child-error form-wrapper--child-error" data-drupal-selector="edit-author" id="edit-author" open="open" data-child-error-count="3">');
+    $open_details = $this->cssSelect('details[open="open"]');
     // Only one extra details element should now be open.
-    $open_details_elements++;
-    $this->assertEqual(count($this->cssSelect('details[open="open"]')), $open_details_elements, 'Exactly one extra open &lt;details&gt; element found.');
+    $open_details_count++;
+    $this->assertEqual(count($open_details), $open_details_count, 'Exactly one extra open &lt;details&gt; element found.');
+    // The details summary should indicate that there is an error.
+    $web_assert->elementTextContains('css', 'details.node-form-author .child-error-indicator', '(contains error)');
 
     // Edit the same node, save it and verify it's unpublished after unchecking
     // the 'Published' boolean_checkbox and clicking 'Save'.
diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml
index 9e178cd51a..b4e468ff7b 100644
--- a/core/modules/system/tests/modules/form_test/form_test.routing.yml
+++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml
@@ -467,6 +467,14 @@ form_test.group_vertical_tabs:
   requirements:
     _access: 'TRUE'
 
+form_test.child_error_vertical_tabs:
+  path: '/form-test/child-error-vertical-tabs'
+  defaults:
+    _form: '\Drupal\form_test\Form\FormTestChildErrorVerticalTabsForm'
+    _title: 'Child element error vertical tabs testing'
+  requirements:
+    _access: 'TRUE'
+
 form_test.two_instances:
   path: '/form-test/two-instances-of-same-form'
   defaults:
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestChildErrorVerticalTabsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestChildErrorVerticalTabsForm.php
new file mode 100644
index 0000000000..7de7d01fa2
--- /dev/null
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestChildErrorVerticalTabsForm.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\form_test\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Builds a form to test child element errors on #type 'vertical_tabs'.
+ */
+class FormTestChildErrorVerticalTabsForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'form_test_group_vertical_tabs';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['vertical_tabs'] = [
+      '#type' => 'vertical_tabs',
+    ];
+    $form['meta'] = [
+      '#type' => 'details',
+      '#title' => 'First group element',
+      '#group' => 'vertical_tabs',
+    ];
+    $form['meta']['element'] = [
+      '#type' => 'textfield',
+      '#title' => 'First nested element in details element',
+      '#required' => TRUE,
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Submit',
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/VerticalTabsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/VerticalTabsTest.php
new file mode 100644
index 0000000000..9abaf1a705
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Render/Element/VerticalTabsTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests\Core\Render\Element;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests that child errors are displayed on vertical tabs.
+ *
+ * @group Render
+ */
+class VerticalTabsTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['form_test'];
+
+  /**
+   * Tests that a child error is indicated on a vertical tab.
+   */
+  public function testChildError() {
+    $web_assert = $this->assertSession();
+
+    $this->drupalGet('form-test/child-error-vertical-tabs');
+
+    $this->submitForm([], 'Submit');
+
+    $title_element = $web_assert->waitForElement('css', '.vertical-tabs__menu-item-title .child-error-indicator');
+
+    $this->assertEquals('(contains error)', $title_element->getText());
+  }
+
+}
diff --git a/core/themes/bartik/bartik.libraries.yml b/core/themes/bartik/bartik.libraries.yml
index 3bbecd932f..bace5d0255 100644
--- a/core/themes/bartik/bartik.libraries.yml
+++ b/core/themes/bartik/bartik.libraries.yml
@@ -10,6 +10,7 @@ global-styling:
       css/components/captions.css: {}
       css/components/comments.css: {}
       css/components/contextual.css: {}
+      css/components/details.css: {}
       css/components/demo-block.css: {}
       # @see https://www.drupal.org/node/2389735
       css/components/dropbutton.component.css: {}
diff --git a/core/themes/bartik/css/components/details.css b/core/themes/bartik/css/components/details.css
new file mode 100644
index 0000000000..9efd7b381b
--- /dev/null
+++ b/core/themes/bartik/css/components/details.css
@@ -0,0 +1,32 @@
+/**
+ * @file
+ * Collapsible details.
+ *
+ * @see collapse.js
+ */
+
+/* Errors */
+.details--child-error:not([open]) summary {
+  background-color: #f1f1f1;
+}
+.details--child-error:not([open]) summary:before {
+  content: '';
+  height: 20px;
+  width: 14px;
+  background: url(../../../../misc/icons/e32700/error.svg) no-repeat center;
+  background-size: contain;
+  float: right; /* LTR */
+}
+[dir="rtl"] .details--child-error:not([open]) summary:before {
+  float: left;
+}
+.details--child-error:not([open]) {
+  border: 2px solid red;
+  box-shadow: inset 7px 0 0 red; /* LTR */
+  padding-left: 7px; /* LTR */
+}
+[dir="rtl"] .details--child-error:not([open]) {
+  box-shadow: inset -7px 0 0 red;
+  padding-left: 0;
+  padding-right: 7px;
+}
diff --git a/core/themes/bartik/css/components/vertical-tabs.component.css b/core/themes/bartik/css/components/vertical-tabs.component.css
index ce4d6cdc3d..20b06bd6d7 100644
--- a/core/themes/bartik/css/components/vertical-tabs.component.css
+++ b/core/themes/bartik/css/components/vertical-tabs.component.css
@@ -14,3 +14,39 @@
   /* This is required to win specificity over [dir="rtl"] .region-content ul */
   padding: 0;
 }
+.vertical-tab__menu-item--child-error.is-selected {
+  padding-top: 1px;
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) {
+  border-top: 1px solid red;
+  background-color: #f1f1f1;
+  box-shadow: inset 7px 0 0 red; /* LTR */
+  border-radius: 2px 0 0 2px; /* LTR */
+  padding-left: 7px; /* LTR */
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) a {
+  padding-left: 7px; /* LTR */
+  border-bottom: 1px solid red;
+  color: #e32700;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) {
+  box-shadow: inset -7px 0 0 red;
+  border-radius: 0 2px 2px 0;
+  padding-left: 0;
+  padding-right: 7px;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) a {
+  padding-left: 7px;
+  padding-right: 7px;
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) .vertical-tabs__menu-item-title:before {
+  content: '';
+  height: 20px;
+  width: 14px;
+  background: url(../../../../misc/icons/e32700/error.svg) no-repeat center;
+  background-size: contain;
+  float: right;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) .vertical-tabs__menu-item-title:before {
+  float: left;
+}
diff --git a/core/themes/seven/css/components/details.css b/core/themes/seven/css/components/details.css
new file mode 100644
index 0000000000..4b34c03d4e
--- /dev/null
+++ b/core/themes/seven/css/components/details.css
@@ -0,0 +1,32 @@
+/**
+ * @file
+ * Collapsible details.
+ *
+ * @see collapse.js
+ */
+
+/* Errors */
+.details--child-error:not([open]) summary:before {
+  content: '';
+  height: 16px;
+  width: 14px;
+  background: url(../../../../misc/icons/e32700/error.svg) no-repeat center;
+  background-size: contain;
+  float: right; /* LTR */
+}
+[dir="rtl"] .details--child-error:not([open]) summary:before {
+  float: left;
+}
+.details--child-error:not([open]) {
+  border: 1px solid #e62600;
+  box-shadow: inset 7px 0 0 #e62600; /* LTR */
+  border-radius: 2px 0 0 2px; /* LTR */
+  padding-left: 7px; /* LTR */
+  color: #e62600;
+}
+[dir="rtl"] .details--child-error:not([open]) {
+  box-shadow: inset -7px 0 0 #e62600;
+  border-radius: 0 2px 2px 0;
+  padding-left: 0;
+  padding-right: 7px;
+}
diff --git a/core/themes/seven/css/components/entity-meta.css b/core/themes/seven/css/components/entity-meta.css
index f6999beabe..91b02f6e0d 100644
--- a/core/themes/seven/css/components/entity-meta.css
+++ b/core/themes/seven/css/components/entity-meta.css
@@ -59,3 +59,21 @@
 .entity-meta details .summary {
   display: none; /* Hide JS summaries. @todo Rethink summaries. */
 }
+.entity-meta .details--child-error:not([open]) {
+  padding-left: 6px;
+}
+.entity-meta .details--child-error:not([open]) > * {
+  position: relative;
+  left: -7px;
+  margin-right: -12px;
+}
+[dir="rtl"] .entity-meta details.error:not([open]) {
+  padding-left: 0;
+  padding-right: 7px;
+}
+[dir="rtl"] .entity-meta details.error:not([open]) > * {
+  right: -8px;
+  left: auto;
+  margin-left: -12px;
+  margin-right: 0;
+}
diff --git a/core/themes/seven/css/components/vertical-tabs.css b/core/themes/seven/css/components/vertical-tabs.css
index 34b5a52a15..a3101fa6a7 100644
--- a/core/themes/seven/css/components/vertical-tabs.css
+++ b/core/themes/seven/css/components/vertical-tabs.css
@@ -37,6 +37,43 @@
 .vertical-tabs__menu-item.last {
   border-bottom: none;
 }
+.vertical-tab__menu-item--child-error.is-selected {
+  padding-top: 1px;
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) {
+  border: 1px solid #e62600;
+  border-right: 0;
+  border-bottom-color: #ccc;
+  box-shadow: inset 7px 0 0 #e62600; /* LTR */
+  border-radius: 2px 0 0 2px; /* LTR */
+  padding-left: 7px; /* LTR */
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) a {
+  padding-left: 7px; /* LTR */
+  padding-right: 10px;
+  border-bottom: 1px solid #e62600;
+  color: #e62600;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) {
+  box-shadow: inset -7px 0 0 #e62600;
+  border-radius: 0 2px 2px 0;
+  padding-left: 0;
+  padding-right: 7px;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) a {
+  padding-right: 7px;
+}
+.vertical-tab__menu-item--child-error:not(.is-selected) .vertical-tabs__menu-item-title:before {
+  content: '';
+  height: 14px;
+  width: 14px;
+  background: url(../../../../misc/icons/e32700/error.svg) no-repeat center;
+  background-size: contain;
+  float: right;
+}
+[dir="rtl"] .vertical-tab__menu-item--child-error:not(.is-selected) .vertical-tabs__menu-item-title:before {
+  float: left;
+}
 [dir="rtl"] .vertical-tabs__menu-item.is-selected {
   border-left: 1px solid #fcfcfa;
   border-right: none;
diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml
index 508c0fe497..63cd19bfd5 100644
--- a/core/themes/seven/seven.libraries.yml
+++ b/core/themes/seven/seven.libraries.yml
@@ -13,6 +13,7 @@ global-styling:
       css/components/breadcrumb.css: {}
       css/components/buttons.css: {}
       css/components/messages.css: {}
+      css/components/details.css: {}
       css/components/dropbutton.component.css: {}
       css/components/entity-meta.css: {}
       css/components/field-ui.css: {}
