diff --git a/core/includes/form.inc b/core/includes/form.inc
index 838a156..9b9283b 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -361,35 +361,32 @@ function form_get_options($element, $key) {
 function template_preprocess_fieldset(&$variables) {
   $element = $variables['element'];
   Element::setAttributes($element, array('id'));
-  Element\RenderElement::setAttributes($element, array('form-wrapper'));
   $variables['attributes'] = $element['#attributes'];
-  $variables['attributes']['class'][] = 'form-item';
   $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
   $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
+  $variables['title_display'] = isset($element['#title_display']) ? $element['#title_display'] : NULL;
   $variables['children'] = $element['#children'];
-  $legend_attributes = array();
-  if (isset($element['#title_display']) && $element['#title_display'] == 'invisible') {
-    $legend_attributes['class'][] = 'visually-hidden';
-  }
-  $variables['legend']['attributes'] = new Attribute($legend_attributes);
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
+
   $variables['legend']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
-  $legend_span_attributes = array('class' => array('fieldset-legend'));
-  if (!empty($element['#required'])) {
-    $legend_span_attributes['class'][] = 'form-required';
-    $variables['legend_span']['attributes'] = new Attribute($legend_span_attributes);
-  }
+  $variables['legend']['attributes'] = new Attribute();
+
+  $variables['legend_span']['attributes'] = new Attribute();
+
   if (!empty($element['#description'])) {
     $description_id = $element['#attributes']['id'] . '--description';
-    $description_attributes = array(
-      'class' => 'description',
-      'id' => $description_id,
-    );
+    $description_attributes['id'] = $description_id;
     $variables['description']['attributes'] = new Attribute($description_attributes);
     $variables['description']['content'] = $element['#description'];
 
     // Add the description's id to the fieldset aria attributes.
     $variables['attributes']['aria-describedby'] = $description_id;
   }
+
+  // Pass element's #type to template.
+  if (!empty($element['#type'])) {
+    $variables['type'] = $element['#type'];
+  }
 }
 
 /**
@@ -438,10 +435,6 @@ function template_preprocess_radios(&$variables) {
   if (isset($element['#id'])) {
     $variables['attributes']['id'] = $element['#id'];
   }
-  $variables['attributes']['class'][] = 'form-radios';
-  if (!empty($element['#attributes']['class'])) {
-    $variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
-  }
   if (isset($element['#attributes']['title'])) {
     $variables['attributes']['title'] = $element['#attributes']['title'];
   }
@@ -464,11 +457,6 @@ function template_preprocess_checkboxes(&$variables) {
   if (isset($element['#id'])) {
     $variables['attributes']['id'] = $element['#id'];
   }
-  $variables['attributes']['class'] = array();
-  $variables['attributes']['class'][] = 'form-checkboxes';
-  if (!empty($element['#attributes']['class'])) {
-    $variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
-  }
   if (isset($element['#attributes']['title'])) {
     $variables['attributes']['title'] = $element['#attributes']['title'];
   }
@@ -545,18 +533,11 @@ function template_preprocess_textarea(&$variables) {
   $element = $variables['element'];
   Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
   Element\RenderElement::setAttributes($element, array('form-textarea'));
-  $variables['wrapper_attributes'] = new Attribute(array(
-    'class' => array('form-textarea-wrapper'),
-  ));
-
-  // Add resizable behavior.
-  if (!empty($element['#resizable'])) {
-    $element['#attributes']['class'][] = 'resize-' . $element['#resizable'];
-  }
-
+  $variables['wrapper_attributes'] = new Attribute();
   $variables['attributes'] = new Attribute($element['#attributes']);
-
   $variables['value'] = String::checkPlain($element['#value']);
+  $variables['resizable'] = !empty($element['#resizable']) ? $element['#resizable'] : NULL;
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
 }
 
 /**
@@ -629,27 +610,23 @@ function template_preprocess_form_element(&$variables) {
     $variables['attributes']['id'] = $element['#id'];
   }
 
-  // Add element's #type and #name as class to aid with JS/CSS selectors.
-  $variables['attributes']['class'][] = 'form-item';
+  // Pass element's #type and #name to template.
   if (!empty($element['#type'])) {
-    $variables['attributes']['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
+    $variables['type'] = $element['#type'];
   }
   if (!empty($element['#name'])) {
-    $variables['attributes']['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
-  }
-  // Add a class for disabled elements to facilitate cross-browser styling.
-  if (!empty($element['#attributes']['disabled'])) {
-    $variables['attributes']['class'][] = 'form-disabled';
+    $variables['name'] = $element['#name'];
   }
 
+  // Pass element's disabled status to template.
+  $variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
+
   // If #title is not set, we don't display any label.
   if (!isset($element['#title'])) {
     $element['#title_display'] = 'none';
   }
-  // If #title_dislay is not some of the visible options, add a CSS class.
-  if ($element['#title_display'] != 'before' && $element['#title_display'] != 'after') {
-    $variables['attributes']['class'][] = 'form-no-label';
-  }
+
+  $variables['title_display'] = $element['#title_display'];
 
   $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
   $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
@@ -657,10 +634,7 @@ function template_preprocess_form_element(&$variables) {
   $variables['description'] = NULL;
   if (!empty($element['#description'])) {
     $variables['description_display'] = $element['#description_display'];
-    $description_attributes = array('class' => array('description'));
-    if ($element['#description_display'] === 'invisible') {
-      $description_attributes['class'][] = 'visually-hidden';
-    }
+    $description_attributes = [];
     if (!empty($element['#id'])) {
       $description_attributes['id'] = $element['#id'] . '--description';
     }
@@ -702,14 +676,9 @@ function template_preprocess_form_element_label(&$variables) {
   // If title and required marker are both empty, output no label.
   $variables['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
   $variables['attributes'] = array();
-  // Style the label as class option to display inline with the element.
-  if ($element['#title_display'] == 'after') {
-    $variables['attributes']['class'][] = 'option';
-  }
-  // Show label only to screen readers to avoid disruption in visual flows.
-  elseif ($element['#title_display'] == 'invisible') {
-    $variables['attributes']['class'][] = 'visually-hidden';
-  }
+
+  // Pass element's title_display to template.
+  $variables['title_display'] = $element['#title_display'];
 
   // A #for property of a dedicated #type 'label' element as precedence.
   if (!empty($element['#for'])) {
@@ -725,13 +694,8 @@ function template_preprocess_form_element_label(&$variables) {
     $variables['attributes']['for'] = $element['#id'];
   }
 
-  // For required elements a 'form-required' class is appended to the
-  // label attributes.
-  $variables['required'] = FALSE;
-  if (!empty($element['#required'])) {
-    $variables['required'] = TRUE;
-    $variables['attributes']['class'][] = 'form-required';
-  }
+  // Pass element's required to template.
+  $variables['required'] = !empty($element['#required']) ? TRUE : NULL;
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php b/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php
index 4c1855a..92de0c8 100644
--- a/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php
+++ b/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php
@@ -33,8 +33,6 @@ public static function preRenderCompositeFormElement($element) {
     if (isset($element['#title']) || isset($element['#description'])) {
       // @see #type 'fieldgroup'
       $element['#theme_wrappers'][] = 'fieldset';
-      $element['#attributes']['class'][] = 'fieldgroup';
-      $element['#attributes']['class'][] = 'form-composite';
     }
     return $element;
   }
diff --git a/core/modules/system/templates/checkboxes.html.twig b/core/modules/system/templates/checkboxes.html.twig
index 00384d3..7c56355 100644
--- a/core/modules/system/templates/checkboxes.html.twig
+++ b/core/modules/system/templates/checkboxes.html.twig
@@ -14,4 +14,4 @@
  @todo: remove this file once http://drupal.org/node/1819284 is resolved.
  This is identical to core/modules/system/templates/container.html.twig
 #}
-<div{{ attributes }}>{{ children }}</div>
+<div{{ attributes.addClass('form-checkboxes') }}>{{ children }}</div>
diff --git a/core/modules/system/templates/fieldset.html.twig b/core/modules/system/templates/fieldset.html.twig
index 52d410f..bacdfd7 100644
--- a/core/modules/system/templates/fieldset.html.twig
+++ b/core/modules/system/templates/fieldset.html.twig
@@ -7,6 +7,7 @@
  * - attributes: HTML attributes for the fieldset element.
  * - required: The required marker or empty if the associated fieldset is
  *   not required.
+ * - type: The type of the element.
  * - legend: The legend element containing the following properties:
  *   - title: Title of the fieldset, intended for use as the text of the legend.
  *   - attributes: HTML attributes to apply to the legend.
@@ -22,10 +23,26 @@
  * @ingroup themeable
  */
 #}
-<fieldset{{ attributes }}>
+{% if type in ['radios', 'checkboxes'] %}
+   {%
+     set composite_element_classes = [
+        'fieldgroup',
+        'form-composite',
+      ]
+   %}
+{% endif %}
+{%
+  set legend_span_classes = [
+    'fieldset-legend',
+    required ? 'form-required',
+  ]
+%}
+<fieldset{{ attributes.addClass('form-item', 'form-wrapper', composite_element_classes) }}>
   {% if legend.title is not empty or required -%}
     {#  Always wrap fieldset legends in a SPAN for CSS positioning. #}
-    <legend{{ legend.attributes }}><span class="{{ legend_span.attributes.class }}">{{ legend.title }}{{ required }}</span></legend>
+    <legend{{ title_display == 'invisible' ? legend.attributes.addClass('visually-hidden') : legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
   {%- endif %}
   <div class="fieldset-wrapper">
     {% if prefix %}
@@ -36,7 +53,7 @@
       <span class="field-suffix">{{ suffix }}</span>
     {% endif %}
     {% if description.content %}
-      <div{{ description.attributes }}>{{ description.content }}</div>
+      <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div>
     {% endif %}
   </div>
 </fieldset>
diff --git a/core/modules/system/templates/form-element-label.html.twig b/core/modules/system/templates/form-element-label.html.twig
index 37a0095..6852ead 100644
--- a/core/modules/system/templates/form-element-label.html.twig
+++ b/core/modules/system/templates/form-element-label.html.twig
@@ -5,6 +5,7 @@
  *
  * Available variables:
  * - title: The label's text.
+ * - title_display: Element's title_display setting.
  * - required: An indicator for whether the associated form element is required.
  * - attributes: A list of HTML attributes for the label.
  *
@@ -13,6 +14,13 @@
  * @ingroup themeable
  */
 #}
+{%
+  set classes = [
+    title_display == 'after' ? 'option',
+    title_display == 'invisible' ? 'visually-hidden',
+    required ? 'form-required',
+  ]
+%}
 {% if title is not empty or required -%}
-  <label{{ attributes }}>{{ title }}</label>
+  <label{{ attributes.addClass(classes) }}>{{ title }}</label>
 {%- endif %}
diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig
index 4c359ba..19a23af 100644
--- a/core/modules/system/templates/form-element.html.twig
+++ b/core/modules/system/templates/form-element.html.twig
@@ -9,6 +9,8 @@
  * - suffix: (optional) The form element suffix, may not be set.
  * - required: The required marker, or empty if the associated form element is
  *   not required.
+ * - type: The type of the element.
+ * - name: The name of the element.
  * - label: A rendered label element.
  * - label_display: Label display setting. It can have these values:
  *   - before: The label is output before the element. This is the default.
@@ -36,13 +38,30 @@
  *     value.
  *   - invisible: The description is output after the element, hidden visually
  *     but available to screen readers.
+ * - disabled: True if the element is disabled.
+ * - title_display: Title display setting.
  *
  * @see template_preprocess_form_element()
  *
  * @ingroup themeable
  */
 #}
-<div{{ attributes }}>
+{%
+  set classes = [
+    'form-item',
+    'form-type-' ~ type|clean_class,
+    'form-item-' ~ name|clean_class,
+    title_display not in ['after', 'before'] ? 'form-no-label',
+    disabled == 'disabled' ? 'form-disabled',
+  ]
+%}
+{%
+  set description_classes = [
+    'description',
+    description_display == 'invisible' ? 'visually-hidden',
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
   {% if label_display in ['before', 'invisible'] %}
     {{ label }}
   {% endif %}
@@ -62,7 +81,7 @@
     {{ label }}
   {% endif %}
   {% if description_display in ['after', 'invisible'] and description.content %}
-    <div{{ description.attributes }}>
+    <div{{ description.attributes.addClass(description_classes) }}>
       {{ description.content }}
     </div>
   {% endif %}
diff --git a/core/modules/system/templates/radios.html.twig b/core/modules/system/templates/radios.html.twig
index e397644..bf38830 100644
--- a/core/modules/system/templates/radios.html.twig
+++ b/core/modules/system/templates/radios.html.twig
@@ -12,4 +12,4 @@
  * @ingroup themeable
  */
 #}
-<div{{ attributes }}>{{ children }}</div>
+<div{{ attributes.addClass('form-radios') }}>{{ children }}</div>
diff --git a/core/modules/system/templates/textarea.html.twig b/core/modules/system/templates/textarea.html.twig
index a12d04d..a917fdf 100644
--- a/core/modules/system/templates/textarea.html.twig
+++ b/core/modules/system/templates/textarea.html.twig
@@ -6,6 +6,8 @@
  * Available variables
  * - wrapper_attributes: A list of HTML attributes for the wrapper element.
  * - attributes: A list of HTML attributes for the textarea element.
+ * - resizable: Resizable setting value.
+ * - required: Required setting value.
  * - value: The textarea content.
  *
  * @see template_preprocess_textarea()
@@ -13,4 +15,13 @@
  * @ingroup themeable
  */
 #}
-<div{{ wrapper_attributes }}><textarea{{ attributes }}>{{ value }}</textarea></div>
+{%
+  set classes = [
+    'form-textarea',
+    resizable ? 'resize-' ~ resizable,
+    required ? 'required',
+  ]
+%}
+<div{{ wrapper_attributes.addClass('form-textarea-wrapper') }}>
+  <textarea{{ attributes.addClass(classes) }}>{{ value }}</textarea>
+</div>
