diff --git a/core/modules/views/templates/views-view-grid.html.twig b/core/modules/views/templates/views-view-grid.html.twig
index a5a813c..4ef3dfa 100644
--- a/core/modules/views/templates/views-view-grid.html.twig
+++ b/core/modules/views/templates/views-view-grid.html.twig
@@ -22,15 +22,40 @@
  * @ingroup themeable
  */
 #}
+{%
+  set classes = [
+    'views-view-grid',
+    options.alignment,
+    'cols-' ~ options.columns,
+    'clearfix',
+  ]
+%}
 {% if title %}
   <h3>{{ title }}</h3>
 {% endif %}
-<div{{ attributes }}>
+<div{{ attributes.addClass(classes) }}>
 {% if options.alignment == 'horizontal' %}
   {% for row in items %}
-  <div{{ row.attributes }}>
+  {% if options.row_class_default %}
+    {%
+      set row_classes = [
+        'views-row',
+        'row-' ~ loop.index,
+        'clearfix',
+      ]
+    %}
+  {% endif %}
+  <div{{ row.attributes.addClass(row_classes) }}>
     {% for column in row.content %}
-    <div{{ column.attributes }}>
+    {% if options.col_class_default %}
+      {%
+        set col_classes = [
+          'views-col',
+          'col-' ~ loop.index,
+        ]
+      %}
+    {% endif %}
+    <div{{ column.attributes.addClass(col_classes) }}>
       {{ column.content }}
     </div>
     {% endfor %}
@@ -38,12 +63,29 @@
   {% endfor %}
 {% else %}
   {% for column in items %}
-  <div{{ column.attributes }}>
-    {% for row in column.content %}
-      <div{{ row.attributes }}>
-        {{ row.content }}
-      </div>
-    {% endfor %}
+    {% if options.col_class_default %}
+      {%
+        set col_classes = [
+          'views-col',
+          'col-' ~ loop.index,
+          'clearfix',
+        ]
+      %}
+    {% endif %}
+    <div{{ column.attributes.addClass(col_classes) }}>
+      {% for row in column.content %}
+        {% if options.row_class_default %}
+          {%
+            set row_classes = [
+              'views-row',
+              'row-' ~ loop.index,
+            ]
+          %}
+        {% endif %}
+        <div{{ row.attributes.addClass(row_classes) }}>
+          {{ row.content }}
+        </div>
+      {% endfor %}
     </div>
   {% endfor %}
 {% endif %}
diff --git a/core/modules/views/templates/views-view-summary-unformatted.html.twig b/core/modules/views/templates/views-view-summary-unformatted.html.twig
index 9120dd4..5564750 100644
--- a/core/modules/views/templates/views-view-summary-unformatted.html.twig
+++ b/core/modules/views/templates/views-view-summary-unformatted.html.twig
@@ -9,6 +9,7 @@
  *   - count: The number of items this summary item represents.
  *   - separator: A separator between each row.
  *   - attributes: HTML attributes for a row.
+ *   - active: A flag indicating whtether the row is active.
  * - options: Flags indicating how each row should be displayed. This contains:
  *   - count: A flag indicating whether the row's 'count' should be displayed.
  *   - inline: A flag indicating whether the item should be wrapped in an inline
@@ -20,11 +21,16 @@
  */
 #}
 {% for row in rows  %}
+  {%
+    set row_classes = [
+      row.active ? 'active',
+    ]
+  %}
   {{ options.inline ? '<span' : '<div' }} class="views-summary views-summary-unformatted">
   {% if row.separator -%}
     {{ row.separator }}
   {%- endif %}
-  <a href="{{ row.url }}"{{ row.attributes }}>{{ row.link }}</a>
+  <a href="{{ row.url }}"{{ row.attributes.addClass(row_classes) }}>{{ row.link }}</a>
   {% if options.count %}
     ({{ row.count }})
   {% endif %}
diff --git a/core/modules/views/templates/views-view-summary.html.twig b/core/modules/views/templates/views-view-summary.html.twig
index 4d8ed8f..f7f0fcf 100644
--- a/core/modules/views/templates/views-view-summary.html.twig
+++ b/core/modules/views/templates/views-view-summary.html.twig
@@ -10,6 +10,7 @@
  *   - link: The summary link text.
  *   - count: The number of items under this grouping.
  *   - attributes: HTML attributes to apply to each row.
+ *   - active: A flag indicating whtether the row is active.
  * - options: Flags indicating how the summary should be displayed.
  *   This contains:
  *   - count: A flag indicating whether the count should be displayed.
@@ -22,7 +23,12 @@
 <div class="item-list">
   <ul class="views-summary">
   {% for row in rows %}
-    <li><a href="{{ row.url }}"{{ row.attributes }}>{{ row.link }}</a>
+    {%
+      set row_classes = [
+        row.active ? 'active',
+      ]
+    %}
+    <li><a href="{{ row.url }}"{{ row.attributes.addClass(row_classes) }}>{{ row.link }}</a>
       {% if options.count %}
         ({{ row.count }})
       {% endif %}
diff --git a/core/modules/views/templates/views-view-table.html.twig b/core/modules/views/templates/views-view-table.html.twig
index 6b0b26c..ceddc45 100644
--- a/core/modules/views/templates/views-view-table.html.twig
+++ b/core/modules/views/templates/views-view-table.html.twig
@@ -20,13 +20,24 @@
  *   - columns: Row column items. Columns are keyed by column number.
  *     - attributes: HTML classes to apply to each column.
  *     - content: The column content.
+ * - responsive: A flag indicating whether table is responsive.
+ * - sticky: A flag indicating whether table header is sticky.
  *
  * @see template_preprocess_views_view_table()
  *
  * @ingroup themeable
  */
 #}
-<table{{ attributes }}>
+{%
+  set classes = [
+    'views-table',
+    'views-view-table',
+    'cols-' ~ header|length,
+    responsive ? 'responsive-enabled',
+    sticky ? 'sticky-enabled',
+  ]
+%}
+<table{{ attributes.addClass(classes) }}>
   {% if caption_needed %}
     <caption>
     {% if caption %}
diff --git a/core/modules/views/templates/views-view-unformatted.html.twig b/core/modules/views/templates/views-view-unformatted.html.twig
index e3fb2e3..68c6e02 100644
--- a/core/modules/views/templates/views-view-unformatted.html.twig
+++ b/core/modules/views/templates/views-view-unformatted.html.twig
@@ -8,6 +8,7 @@
  * - rows: A list of the view's row items.
  *   - attributes: The row's HTML attributes.
  *   - content: The row's content.
+ * - view: The view object.
  *
  * @see template_preprocess_views_view_unformatted()
  *
@@ -18,7 +19,12 @@
   <h3>{{ title }}</h3>
 {% endif %}
 {% for row in rows %}
-  <div{{ row.attributes }}>
+  {%
+    set row_classes = [
+      default_row_class ? 'views-row',
+    ]
+  %}
+  <div{{ row.attributes.addClass(row_classes) }}>
     {{ row.content }}
   </div>
 {% endfor %}
diff --git a/core/modules/views/templates/views-view.html.twig b/core/modules/views/templates/views-view.html.twig
index f839ef9..18fbdb0 100644
--- a/core/modules/views/templates/views-view.html.twig
+++ b/core/modules/views/templates/views-view.html.twig
@@ -37,7 +37,16 @@
  * @ingroup themeable
  */
 #}
-<div{{ attributes }}>
+{%
+  set classes = [
+    'view',
+    'view-' ~ id|clean_class,
+    'view-id-' ~ id,
+    'view-display-id-' ~ display_id,
+    dom_id ? 'view-dom-id-' ~ dom_id,
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
   {{ title_prefix }}
   {% if title %}
     {{ title }}
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 06ccdca..c29fdfc 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -29,13 +29,6 @@ function template_preprocess_views_view(&$variables) {
   $variables['id'] = $id;
   $variables['display_id'] = $view->current_display;
 
-  // Basic classes.
-  $variables['attributes']['class'] = array();
-  $variables['attributes']['class'][] = 'view';
-  $variables['attributes']['class'][] = 'view-' . Html::cleanCssIdentifier($variables['id']);
-  $variables['attributes']['class'][] = 'view-id-' . $variables['id'];
-  $variables['attributes']['class'][] = 'view-display-id-' . $variables['display_id'];
-
   $css_class = $view->display_handler->getOption('css_class');
   if (!empty($css_class)) {
     $variables['css_class'] = preg_replace('/[^a-zA-Z0-9- ]/', '-', $css_class);
@@ -55,7 +48,6 @@ function template_preprocess_views_view(&$variables) {
     // identifier for each view. This identifier is written to both
     // drupalSettings and the DIV wrapper.
     $variables['dom_id'] = $view->dom_id;
-    $variables['attributes']['class'][] = 'view-dom-id-' . $variables['dom_id'];
   }
 }
 
@@ -330,10 +322,8 @@ function template_preprocess_views_view_summary(&$variables) {
     }
     $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options);
     $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
-    if (isset($active_urls[$variables['rows'][$id]->url])) {
-      $variables['rows'][$id]->attributes['class'][] = 'active';
-    }
     $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
+    $variables['rows'][$id]->active = isset($active_urls[$variables['rows'][$id]->url]);
   }
 }
 
@@ -394,9 +384,7 @@ function template_preprocess_views_view_summary_unformatted(&$variables) {
     }
     $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options);
     $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
-    if (isset($active_urls[$variables['rows'][$id]->url])) {
-      $variables['rows'][$id]->attributes['class'][] = 'active';
-    }
+    $variables['rows'][$id]->active = isset($active_urls[$variables['rows'][$id]->url]);
     $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
   }
 }
@@ -615,8 +603,6 @@ function template_preprocess_views_view_table(&$variables) {
     $variables['rows'][$num]['attributes'] = new Attribute($variables['rows'][$num]['attributes']);
   }
 
-  $variables['attributes']['class'][] = 'views-table';
-  $variables['attributes']['class'][] = 'views-view-table';
   if (empty($variables['rows']) && !empty($options['empty_table'])) {
     $build = $view->display_handler->renderArea('empty');
     $variables['rows'][0]['columns'][0]['content'] = drupal_render($build);
@@ -628,11 +614,11 @@ function template_preprocess_views_view_table(&$variables) {
     ));
   }
 
+  $variables['sticky'] = FALSE;
   if (!empty($options['sticky'])) {
     $variables['view']->element['#attached']['library'][] = 'core/drupal.tableheader';
-    $variables['attributes']['class'][] = "sticky-enabled";
+    $variables['sticky'] = TRUE;
   }
-  $variables['attributes']['class'][] = 'cols-' . count($variables['header']);
 
   // Add the caption to the list if set.
   if (!empty($handler->options['caption'])) {
@@ -648,6 +634,7 @@ function template_preprocess_views_view_table(&$variables) {
   $variables['description'] = $handler->options['description'];
   $variables['caption_needed'] |= !empty($variables['summary']) || !empty($variables['description']);
 
+  $variables['responsive'] = FALSE;
   // If the table has headers and it should react responsively to columns hidden
   // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
   // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
@@ -655,7 +642,7 @@ function template_preprocess_views_view_table(&$variables) {
     $variables['view']->element['#attached']['library'][] = 'core/drupal.tableresponsive';
     // Add 'responsive-enabled' class to the table to identify it for JS.
     // This is needed to target tables constructed by this function.
-    $variables['attributes']['class'][] = 'responsive-enabled';
+    $variables['responsive'] = TRUE;
   }
 }
 
@@ -673,13 +660,6 @@ function template_preprocess_views_view_grid(&$variables) {
   $options = $variables['options'] = $variables['view']->style_plugin->options;
   $horizontal = ($options['alignment'] === 'horizontal');
 
-  $variables['attributes']['class'] = array(
-    'views-view-grid',
-    $options['alignment'],
-    'cols-' . $options['columns'],
-    'clearfix',
-  );
-
   $col = 0;
   $row = 0;
   $items = array();
@@ -700,14 +680,6 @@ function template_preprocess_views_view_grid(&$variables) {
     // Create attributes for rows.
     if (!$horizontal || ($horizontal && empty($items[$row]['attributes']))) {
       $row_attributes = array('class' => array());
-      // Add default views row classes.
-      if ($options['row_class_default']) {
-        $row_attributes['class'][] = 'views-row';
-        $row_attributes['class'][] = 'row-' . ($row + 1);
-        if ($horizontal) {
-          $row_attributes['class'][] = 'clearfix';
-        }
-      }
       // Add custom row classes.
       $row_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'row')));
       if (!empty($row_class)) {
@@ -726,13 +698,6 @@ function template_preprocess_views_view_grid(&$variables) {
     if ($horizontal || (!$horizontal && empty($items[$col]['attributes']))) {
       $col_attributes = array('class' => array());
       // Add default views column classes.
-      if ($options['col_class_default']) {
-        $col_attributes['class'][] = 'views-col';
-        $col_attributes['class'][] = 'col-' . ($col + 1);
-        if (!$horizontal) {
-          $col_attributes['class'][] = 'clearfix';
-        }
-      }
       // Add custom column classes.
       $col_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'col')));
       if (!empty($col_class)) {
@@ -798,18 +763,15 @@ function template_preprocess_views_view_unformatted(&$variables) {
   $style = $view->style_plugin;
   $options = $style->options;
 
-  $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : FALSE;
+  $variables['default_row_class'] = isset($options['default_row_class']) ? $options['default_row_class'] : FALSE;
   foreach ($rows as $id => $row) {
     $variables['rows'][$id] = array();
     $variables['rows'][$id]['content'] = $row;
     $variables['rows'][$id]['attributes'] = array();
-    if ($default_row_class) {
-      $variables['rows'][$id]['attributes']['class'][] = 'views-row';
-    }
+    $variables['rows'][$id]['attributes'] = new Attribute($variables['rows'][$id]['attributes']);
     if ($row_class = $view->style_plugin->getRowClass($id)) {
-      $variables['rows'][$id]['attributes']['class'][] = $row_class;
+      $variables['rows'][$id]['attributes']->addClass($row_class);
     }
-    $variables['rows'][$id]['attributes'] = new Attribute($variables['rows'][$id]['attributes']);
   }
 }
 
