diff --git a/core/includes/form.inc b/core/includes/form.inc
index c33ff6f..d555be6 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -951,30 +951,49 @@ function theme_fieldset($variables) {
   element_set_attributes($element, array('id'));
   _form_set_attributes($element, array('form-wrapper'));
 
+  $element['#attributes']['class'][] = 'form-item';
+
   if (!empty($element['#description'])) {
     $description_id = $element['#attributes']['id'] . '--description';
     $element['#attributes']['aria-describedby'] = $description_id;
   }
 
+  // If the element is required, a required marker is appended to the label.
+  // @see theme_form_element_label()
+  $required = '';
+  if (!empty($element['#required'])) {
+    $marker = array(
+      '#theme' => 'form_required_marker',
+      '#element' => $element,
+    );
+    $required = drupal_render($marker);
+  }
+
   $legend_attributes = array();
   if (isset($element['#title_display']) && $element['#title_display'] == 'invisible') {
     $legend_attributes['class'][] = 'visually-hidden';
   }
 
   $output = '<fieldset' . new Attribute($element['#attributes']) . '>';
-  if (!empty($element['#title'])) {
+
+  if ((isset($element['#title']) && $element['#title'] !== '') || !empty($element['#required'])) {
     // Always wrap fieldset legends in a SPAN for CSS positioning.
-    $output .= '<legend' . new Attribute($legend_attributes) . '><span class="fieldset-legend">' . $element['#title'] . '</span></legend>';
+    $output .= '<legend' . new Attribute($legend_attributes) . '><span class="fieldset-legend">';
+    $output .= t('!title!required', array('!title' => $element['#title'], '!required' => $required));
+    $output .= '</span></legend>';
   }
   $output .= '<div class="fieldset-wrapper">';
-  if (!empty($element['#description'])) {
-    $attributes = array('class' => 'fieldset-description', 'id' => $description_id);
-    $output .= '<div' . new Attribute($attributes) . '>' . $element['#description'] . '</div>';
 
+  if (isset($element['#field_prefix'])) {
+    $output .= '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ';
   }
   $output .= $element['#children'];
-  if (isset($element['#value'])) {
-    $output .= $element['#value'];
+  if (isset($element['#field_suffix'])) {
+    $output .= ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>';
+  }
+  if (!empty($element['#description'])) {
+    $attributes = array('class' => 'description', 'id' => $description_id);
+    $output .= '<div' . new Attribute($attributes) . '>' . $element['#description'] . '</div>';
   }
   $output .= '</div>';
   $output .= "</fieldset>\n";
@@ -1259,7 +1278,10 @@ function form_pre_render_conditional_form_element($element) {
   }
 
   if (isset($element['#title']) || isset($element['#description'])) {
-    $element['#theme_wrappers'][] = 'form_element';
+    // @see #type 'fieldgroup'
+    $element['#theme_wrappers'][] = 'fieldset';
+    $element['#attributes']['class'][] = 'fieldgroup';
+    $element['#attributes']['class'][] = 'form-composite';
   }
   return $element;
 }
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index 9a0153d..57fb42c 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -55,6 +55,7 @@ tr.even .form-item {
   margin-bottom: 0;
   white-space: nowrap;
 }
+.form-composite > .fieldset-wrapper > .description,
 .form-item .description {
   font-size: 0.85em;
 }
@@ -62,6 +63,7 @@ label.option {
   display: inline;
   font-weight: normal;
 }
+.form-composite > legend,
 h4.label {
   font-size: inherit;
   font-weight: bold;
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
index 5424f66..fb84409 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Form;
 
+use Drupal\Component\Utility\String;
 use Drupal\simpletest\WebTestBase;
 
 class FormTest extends WebTestBase {
@@ -94,7 +95,7 @@ function testRequiredFields() {
     $elements['file']['empty_values'] = $empty_strings;
 
     // Regular expression to find the expected marker on required elements.
-    $required_marker_preg = '@<label.*<span class="form-required" aria-hidden="true">\*</span></label>@';
+    $required_marker_preg = '@<(?:label|legend).*<span class="form-required" aria-hidden="true">\*</span>.*</(?:label|legend)>@';
 
     // Go through all the elements and all the empty values for them.
     foreach ($elements as $type => $data) {
@@ -520,7 +521,12 @@ function testDisabledElements() {
 
     // All the elements should be marked as disabled, including the ones below
     // the disabled container.
-    $this->assertEqual(count($disabled_elements), 39, 'The correct elements have the disabled property in the HTML code.');
+    $actual_count = count($disabled_elements);
+    $expected_count = 41;
+    $this->assertEqual($actual_count, $expected_count, String::format('Found @actual elements with disabled property (expected @expected).', array(
+      '@actual' => count($disabled_elements),
+      '@expected' => $expected_count,
+    )));
 
     $this->drupalPostForm(NULL, $edit, t('Submit'));
     $returned_values['hijacked'] = drupal_json_decode($this->content);
diff --git a/core/themes/seven/seven.base.css b/core/themes/seven/seven.base.css
index b72bbce..59f4d5e 100644
--- a/core/themes/seven/seven.base.css
+++ b/core/themes/seven/seven.base.css
@@ -23,7 +23,7 @@ hr {
   background: #cccccc;
 }
 summary,
-legend {
+.fieldgroup:not(.form-composite) > legend {
   font-weight: bold;
   text-transform: uppercase;
 }
