diff --git a/core/includes/form.inc b/core/includes/form.inc
index 2e8e9e1..40103cd 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -1105,6 +1105,7 @@ function form_process_password_confirm($element) {
     '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
     '#required' => $element['#required'],
     '#attributes' => array('class' => array('password-field')),
+    '#error_use_parent' => TRUE,
   );
   $element['pass2'] =  array(
     '#type' => 'password',
@@ -1112,6 +1113,7 @@ function form_process_password_confirm($element) {
     '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
     '#required' => $element['#required'],
     '#attributes' => array('class' => array('password-confirm')),
+    '#error_use_parent' => TRUE,
   );
   $element['#element_validate'] = array('password_confirm_validate');
   $element['#tree'] = TRUE;
@@ -1215,6 +1217,8 @@ function form_process_radios($element) {
         '#parents' => $element['#parents'],
         '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
         '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+        // Errors should only be shown on the parent radios element.
+        '#error_use_parent' => TRUE,
         '#weight' => $weight,
       );
     }
@@ -1365,6 +1369,8 @@ function form_process_checkboxes($element) {
         '#default_value' => isset($value[$key]) ? $key : NULL,
         '#attributes' => $element['#attributes'],
         '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+        // Errors should only be shown on the parent checkboxes element.
+        '#error_use_parent' => TRUE,
         '#weight' => $weight,
       );
     }
@@ -2771,8 +2777,11 @@ function theme_form_element($variables) {
   // may not necessarily have been processed by form_builder().
   $element += array(
     '#title_display' => 'before',
+    '#parents' => array(),
   );
 
+  $variables['errors'] = \Drupal::formBuilder()->getError($element);
+
   // Take over any #wrapper_attributes defined by the element.
   // @todo Temporary hack for #type 'item'.
   // @see http://drupal.org/node/1829202
@@ -2795,8 +2804,17 @@ function theme_form_element($variables) {
   if (!empty($element['#attributes']['disabled'])) {
     $attributes['class'][] = 'form-disabled';
   }
+  // Add a class if an error exists.
+  if (!empty($variables['errors'])) {
+    $attributes['class'][] = 'form-error';
+  }
   $output = '<div' . new Attribute($attributes) . '>' . "\n";
 
+  // Display any error messages.
+  if ($variables['errors'] && !$element['#error_use_parent']) {
+    $output .= ' ' . theme('form_error_message', $variables);
+  }
+
   // If #title is not set, we don't display any label or required marker.
   if (!isset($element['#title'])) {
     $element['#title_display'] = 'none';
@@ -2907,6 +2925,25 @@ function theme_form_element_label($variables) {
 }
 
 /**
+ * Returns HTML for an inline error associated with a specific form element.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: error.
+ *   - errors: The errors associated with the current element as returned by
+ *     form_get_error($element).
+ *
+ * @ingroup themeable
+ */
+function theme_form_error_message($variables) {
+  $output = '<div class="form-error-message">';
+  $output .= '<strong>' . t('Error') . ': ' . $variables['errors'] . '</strong>';
+  $output .= '</div>';
+  return $output;
+}
+
+/**
  * Sets a form element's class attribute.
  *
  * Adds 'required' and 'error' classes as needed.
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 9a0d713..357e7c4 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -3143,6 +3143,9 @@ function drupal_common_theme() {
     'form_element_label' => array(
       'render element' => 'element',
     ),
+    'form_error_message' => array(
+      'render element' => 'element',
+    ),
     'vertical_tabs' => array(
       'render element' => 'element',
     ),
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 38bfab7..156d4c7 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -905,6 +905,9 @@ public function validateForm($form_id, &$form, &$form_state) {
       }
       $form_state['values'] = $values;
     }
+    if (!$form_state['programmed']) {
+      $this->displayErrors($form);
+    }
   }
 
   /**
@@ -1284,6 +1287,7 @@ public function doBuildForm($form_id, &$element, &$form_state) {
       '#required' => FALSE,
       '#attributes' => array(),
       '#title_display' => 'before',
+      '#error_use_parent' => FALSE,
     );
 
     // Special handling if we're on the top level form element.
@@ -1793,4 +1797,82 @@ public function setRequest(Request $request) {
     $this->request = $request;
   }
 
+  /**
+   * Displays the given form's errors and links each error to the form element
+   * in question.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   */
+  protected function displayErrors($form) {
+    if ($errors = $this->getErrors()) {
+      $error_links = array();
+      foreach ($errors as $key => $error) {
+        $element = $this->getElement($key, $form);
+        if ($element) {
+          $title = $this->getElementTitle($element);
+          $error_links[] = l($title, '', array('fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE));
+        }
+        else {
+          drupal_set_message($error, 'error');
+          unset($errors[$key]);
+        }
+      }
+
+      if (!empty($error_links)) {
+        drupal_set_message(format_plural(count($error_links), '1 error has been found', '@count errors have been found') . ': ' . implode(', ', $error_links), 'error');
+      }
+    }
+  }
+
+  /**
+   * Given a form and an element key, this function returns the element no matter
+   * how deep within the form array the key exists. If the key is not found an
+   * empty array is returned.
+   *
+   * @param string $element_key
+   *   The key to search for.
+   *
+   * @param array $form
+   *   A structured form array to search.
+   *
+   * @return array
+   */
+  protected function getElement($element_key, $form) {
+    $element = array();
+    foreach (element_children($form) as $key) {
+      if ($key === $element_key) {
+        $element = $form[$key];
+        break;
+      }
+      else {
+        if (is_array($form[$key])) {
+          $element = $this->getElement($element_key, $form[$key]);
+          if (!empty($element)) {
+            break;
+          }
+        }
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * Returns the title the highest element in the hierarchy that has a title. If
+   * no title is found, then NULL is returned.
+   */
+  protected function getElementTitle(array $element) {
+    if (isset($element['#title'])) {
+      return $element['#title'];
+    }
+    else {
+      foreach (element_children($element) as $key) {
+        $title = $this->elementGetTitle($element[$key]);
+        if (isset($title)) {
+          return $title;
+        }
+      }
+    }
+  }
+
 }
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index c6ed012..e2a6504 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -44,6 +44,16 @@ td.active {
 /**
  * Markup generated by Form API.
  */
+div.form-error {
+  background-color: #fef5f1;
+  border: 1px solid #ed541d;
+  color: #8c2e0b;
+  padding: 10px;
+}
+div.form-error-message {
+  margin-bottom: 10px;
+  min-height: 25px;
+}
 .form-item,
 .form-actions {
   margin-top: 1em;
