diff --git a/core/includes/form.inc b/core/includes/form.inc
index 5b292f5..2d083d4 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Form\FormElementHelper;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
@@ -1151,6 +1152,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',
@@ -1158,6 +1160,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;
@@ -1261,6 +1264,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,
       );
     }
@@ -1335,6 +1340,7 @@ function form_pre_render_conditional_form_element($element) {

   if (isset($element['#title']) || isset($element['#description'])) {
     // @see #type 'fieldgroup'
+    $element['#theme_wrappers'][] = 'form_element';
     $element['#theme_wrappers'][] = 'fieldset';
     $element['#attributes']['class'][] = 'fieldgroup';
     $element['#attributes']['class'][] = 'form-composite';
@@ -1414,6 +1420,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,
       );
     }
@@ -2689,6 +2697,25 @@ function template_preprocess_form(&$variables) {
   }
   $variables['attributes'] = $element['#attributes'];
   $variables['children'] = $element['#children'];
+
+  if (!empty($element['#errors'])) {
+    $error_links = array();
+    // Loop through all form errors, and display a link for each error that
+    // is associated with a specific form element.
+    foreach ($element['#errors'] as $key => $error) {
+      if ($form_element = FormElementHelper::getElementByName($key, $element)) {
+        $title = FormElementHelper::getElementTitle($form_element);
+        $error_links[] = l($title, '', array('fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE));
+      }
+      else {
+        drupal_set_message($error, 'error');
+      }
+    }
+
+    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');
+    }
+  }
 }

 /**
@@ -2882,6 +2909,14 @@ function template_preprocess_form_element(&$variables) {
     $variables['attributes']['class'][] = 'form-disabled';
   }

+  // Display any error messages.
+  $variables['errors'] = NULL;
+  if (!empty($element['#errors']) && empty($element['#error_use_parent'])) {
+    // Add a class if an error exists.
+    $variables['attributes']['class'][] = 'form-error';
+    $variables['errors'] = $element['#errors'];
+  }
+
   // If #title is not set, we don't display any label or required marker.
   if (!isset($element['#title'])) {
     $element['#title_display'] = 'none';
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index aa8369a..0b42dbc 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -875,6 +875,9 @@ public function validateForm($form_id, &$form, &$form_state) {
       }
       $form_state['values'] = $values;
     }
+    if (!$form_state['programmed']) {
+      $form['#errors'] = $this->getErrors($form_state);
+    }
   }

   /**
@@ -1185,6 +1188,16 @@ public function executeHandlers($type, &$form, &$form_state) {
    */
   public function setErrorByName($name, array &$form_state, $message = '') {
     if (!isset($form_state['errors'][$name])) {
+      // This is only used by errors set in submit handlers.
+      // @todo Unlike errors set during validation, these errors will not
+      //   directly correspond to their input element, and will not interrupt
+      //   submission. We should consider limiting usage of form errors to
+      //   validation only, and encourage usage of drupal_set_message() in
+      //   submit handlers.
+      if ($message && isset($form_state['build_info']['form_id']) && !empty($this->validatedForms[$form_state['build_info']['form_id']])) {
+        $this->drupalSetMessage($message, 'error');
+      }
+
       $record = TRUE;
       if (isset($form_state['limit_validation_errors'])) {
         // #limit_validation_errors is an array of "sections" within which user
@@ -1212,9 +1225,6 @@ public function setErrorByName($name, array &$form_state, $message = '') {
       if ($record) {
         $form_state['errors'][$name] = $message;
         $this->request->attributes->set('_form_errors', TRUE);
-        if ($message) {
-          $this->drupalSetMessage($message, 'error');
-        }
       }
     }

diff --git a/core/lib/Drupal/Core/Form/FormElementHelper.php b/core/lib/Drupal/Core/Form/FormElementHelper.php
new file mode 100644
index 0000000..ec5616e
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/FormElementHelper.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\FormElementHelper.
+ */
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides common functionality for form elements.
+ */
+class FormElementHelper {
+
+  /**
+   * Retrieves a form element.
+   *
+   * @param string $name
+   *   The name of the form element. If the #parents property of your form
+   *   element is array('foo', 'bar', 'baz') then the name is 'foo][bar][baz'.
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   *
+   * @return array
+   *   The form element.
+   */
+  public static function getElementByName($name, array $form) {
+    foreach (Element::children($form) as $key) {
+      if ($key === $name) {
+        return $form[$key];
+      }
+      elseif ($element = static::getElementByName($name, $form[$key])) {
+        return $element;
+      }
+    }
+    return array();
+  }
+
+  /**
+   * Returns the title for the element.
+   *
+   * If the element has no title, this will recurse through all children of the
+   * element until a title is found.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the form element.
+   *
+   * @return string
+   *   The title of the element, or an empty string if none is found.
+   */
+  public static function getElementTitle(array $element) {
+    $title = '';
+    if (isset($element['#title'])) {
+      $title = $element['#title'];
+    }
+    else {
+      foreach (Element::children($element) as $key) {
+        if ($title = static::getElementTitle($element[$key])) {
+          break;
+        }
+      }
+    }
+    return $title;
+  }
+
+}
diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc
index 3408d83..e6f3a86 100644
--- a/core/modules/shortcut/shortcut.admin.inc
+++ b/core/modules/shortcut/shortcut.admin.inc
@@ -118,7 +118,7 @@ function shortcut_set_switch_validate($form, &$form_state) {
   if ($form_state['values']['set'] == 'new') {
     // Check to prevent creating a shortcut set with an empty title.
     if (trim($form_state['values']['label']) == '') {
-      form_set_error('new', $form_state, t('The new set label is required.'));
+      form_set_error('label', $form_state, t('The new set label is required.'));
     }
     // Check to prevent a duplicate title.
     if (shortcut_set_title_exists($form_state['values']['label'])) {
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index f397a5a..3e43abb 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.
  */
+.form-error {
+  background-color: #fef5f1;
+  border: 1px solid #ed541d;
+  color: #8c2e0b;
+  padding: 10px;
+}
+.form-error-message {
+  margin-bottom: 10px;
+  min-height: 25px;
+}
 .form-item,
 .form-actions {
   margin-top: 1em;
diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig
index ea4d90f..4c0d8d9 100644
--- a/core/modules/system/templates/form-element.html.twig
+++ b/core/modules/system/templates/form-element.html.twig
@@ -5,6 +5,7 @@
  *
  * Available variables:
  * - attributes: HTML attributes for the containing element.
+ * - errors: (optional) Any errors for this form element, may not be set.
  * - prefix: (optional) The form element prefix, may not be set.
  * - suffix: (optional) The form element suffix, may not be set.
  * - required: The required marker, or empty if the associated form element is
@@ -37,6 +38,11 @@
  */
 #}
 <div{{ attributes }}>
+  {% if errors %}
+    <div class="form-error-message">
+      <strong>{{ errors }}</strong>
+    </div>
+  {% endif %}
   {% if label_display in ['before', 'invisible'] %}
     {{ label }}
   {% endif %}
diff --git a/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php b/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php
new file mode 100644
index 0000000..58973bd
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Form\FormBuilderTest.
+ */
+
+namespace Drupal\Tests\Core\Form;
+
+use Drupal\Core\Form\FormElementHelper;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the form element helper.
+ *
+ * @group Drupal
+ * @group Form
+ *
+ * @coversDefaultClass \Drupal\Core\Form\FormElementHelper
+ */
+class FormElementHelperTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'FormElementHelper test',
+      'description' => 'Tests the form element helper.',
+      'group' => 'Form API',
+    );
+  }
+
+  /**
+   * Tests the getElementByName() method.
+   *
+   * @covers ::getElementByName()
+   *
+   * @dataProvider getElementByNameProvider
+   */
+  public function testGetElementByName($name, $form, $expected) {
+    $this->assertSame($expected, FormElementHelper::getElementByName($name, $form));
+  }
+
+  /**
+   * Provides test data.
+   */
+  public function getElementByNameProvider() {
+    return array(
+      array('id', array(), array()),
+      array('id', array('id' => array('#title' => 'ID')), array('#title' => 'ID')),
+      array('id', array('fieldset' => array('id' => array('#title' => 'ID'))), array('#title' => 'ID')),
+      array('fieldset', array('fieldset' => array('id' => array('#title' => 'ID'))), array('id' => array('#title' => 'ID'))),
+    );
+  }
+
+  /**
+   * Tests the getElementTitle() method.
+   *
+   * @covers ::getElementTitle()
+   *
+   * @dataProvider getElementTitleProvider
+   */
+  public function testGetElementTitle($name, $form, $expected) {
+    $element = FormElementHelper::getElementByName($name, $form);
+    $this->assertSame($expected, FormElementHelper::getElementTitle($element));
+  }
+
+  /**
+   * Provides test data.
+   */
+  public function getElementTitleProvider() {
+    return array(
+      array('id', array(), ''),
+      array('id', array('id' => array('#title' => 'ID')), 'ID'),
+      array('id', array('fieldset' => array('id' => array('#title' => 'ID'))), 'ID'),
+      array('fieldset', array('fieldset' => array('id' => array('#title' => 'ID'))), 'ID'),
+    );
+  }
+
+}
