diff --git a/core/includes/form.inc b/core/includes/form.inc
index 260ffbe..a8413c8 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -303,29 +303,20 @@ 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'];
 
@@ -380,10 +371,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'];
   }
@@ -406,11 +393,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'];
   }
@@ -487,18 +469,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;
 }
 
 /**
@@ -570,27 +545,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 elements #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 elements 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;
@@ -598,10 +569,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';
     }
@@ -643,14 +611,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 elements 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'])) {
@@ -666,13 +629,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 elements required to template.
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
 }
 
 /**
diff --git a/core/modules/locale/src/Tests/LocaleUpdateTest.php b/core/modules/locale/src/Tests/LocaleUpdateTest.php
index 88927f9..ed0203e 100644
--- a/core/modules/locale/src/Tests/LocaleUpdateTest.php
+++ b/core/modules/locale/src/Tests/LocaleUpdateTest.php
@@ -139,7 +139,7 @@ public function testUpdateImportSourceRemote() {
     $this->drupalGet('admin/reports/translations/check');
 
     // Check the status on the Available translation status page.
-    $this->assertRaw('<label class="visually-hidden" for="edit-langcodes-de">Update German</label>', 'German language found');
+    $this->assertRaw('<label for="edit-langcodes-de" class="visually-hidden">Update German</label>', 'German language found');
     $this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found');
     $this->assertText('Contributed module one (' . format_date($this->timestamp_now, 'html_date') . ')', 'Updates for Contrib module one');
     $this->assertText('Contributed module two (' . format_date($this->timestampNew, 'html_date') . ')', 'Updates for Contrib module two');
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..7f03471 100644
--- a/core/modules/system/templates/fieldset.html.twig
+++ b/core/modules/system/templates/fieldset.html.twig
@@ -22,10 +22,17 @@
  * @ingroup themeable
  */
 #}
-<fieldset{{ attributes }}>
-  {% if legend.title is not empty or required -%}
+<fieldset{{ attributes.addClass('form-item', 'form-wrapper') }}>
+    {%
+      set legend_span_classes = [
+        'fieldset-legend',
+        required ? 'form-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{{ legend.attributes }}>
+      <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
+    </legend>
   {%- endif %}
   <div class="fieldset-wrapper">
     {% if prefix %}
@@ -36,7 +43,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..6ececd9 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: Elements 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 5f97dbc..a961801 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.
@@ -35,13 +37,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 %}
@@ -61,7 +80,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..8a8426d 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: An indicator for whether the textarea is resizable.
+ * - required: An indicator for whether the textarea is required.
  * - 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>
