diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 9b89250..cfa4062 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -827,6 +827,8 @@ function template_preprocess_image(&$variables) {
  *       narrow viewports to save horizontal space.
  *     - Any HTML attributes, such as "colspan", to apply to the column header
  *       cell.
+ *   - header_multilevel: A boolean flag, FALSE by default. If set to TRUE, the
+ *       header key array should have one more depth level.
  *   - rows: An array of table rows. Every row is an array of cells, or an
  *     associative array with the following keys:
  *     - data: An array of cells.
@@ -926,57 +928,77 @@ function template_preprocess_table(&$variables) {
   if (!empty($variables['header'])) {
     $ts = tablesort_init($variables['header']);
 
-    // Use a separate index with responsive classes as headers
-    // may be associative.
-    $responsive_index = -1;
-    foreach ($variables['header'] as $col_key => $cell) {
-      // Increase the responsive index.
-      $responsive_index++;
-
-      if (!is_array($cell)) {
-        $header_columns++;
-        $cell_content = $cell;
-        $cell_attributes = new Attribute();
-        $is_header = TRUE;
-      }
-      else {
-        if (isset($cell['colspan'])) {
-          $header_columns += $cell['colspan'];
+    // Build multirows header from simple row.
+    if (empty($variables['header_multilevel'])) {
+      $variables['header'] = [
+        'single_row' => $variables['header'],
+      ];
+    }
+
+    foreach ($variables['header'] as $row_key => $header_row) {
+      $header_columns_in_row = 0;
+
+      // Use a separate index with responsive classes as headers
+      // may be associative.
+      $responsive_index = -1;
+      foreach ($header_row as $col_key => $cell) {
+        // Increase the responsive index.
+        $responsive_index++;
+
+        if (!is_array($cell)) {
+          $header_columns_in_row++;
+          $cell_content = $cell;
+          $cell_attributes = new Attribute();
+          $is_header = TRUE;
         }
         else {
-          $header_columns++;
-        }
-        $cell_content = '';
-        if (isset($cell['data'])) {
-          $cell_content = $cell['data'];
-          unset($cell['data']);
-        }
-        // Flag the cell as a header or not and remove the flag.
-        $is_header = isset($cell['header']) ? $cell['header'] : TRUE;
-        unset($cell['header']);
-
-        // Track responsive classes for each column as needed. Only the header
-        // cells for a column are marked up with the responsive classes by a
-        // module developer or themer. The responsive classes on the header cells
-        // must be transferred to the content cells.
-        if (!empty($cell['class']) && is_array($cell['class'])) {
-          if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
-            $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM;
+          if (isset($cell['colspan'])) {
+            $header_columns_in_row += $cell['colspan'];
           }
-          elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
-            $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW;
+          else {
+            $header_columns_in_row++;
+          }
+          $cell_content = '';
+          if (isset($cell['data'])) {
+            $cell_content = $cell['data'];
+            unset($cell['data']);
+          }
+          // Flag the cell as a header or not and remove the flag.
+          $is_header = isset($cell['header']) ? $cell['header'] : TRUE;
+          unset($cell['header']);
+
+          // Track responsive classes for each column as needed. Only the header
+          // cells for a column are marked up with the responsive classes by a
+          // module developer or themer. The responsive classes on the header cells
+          // must be transferred to the content cells.
+          if (!empty($cell['class']) && is_array($cell['class'])) {
+            if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
+              $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM;
+            }
+            elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
+              $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW;
+            }
           }
-        }
 
-        tablesort_header($cell_content, $cell, $variables['header'], $ts);
+          tablesort_header($cell_content, $cell, $variables['header'], $ts);
 
-        // tablesort_header() removes the 'sort' and 'field' keys.
-        $cell_attributes = new Attribute($cell);
+          // tablesort_header() removes the 'sort' and 'field' keys.
+          $cell_attributes = new Attribute($cell);
+        }
+        $variables['header'][$row_key][$col_key] = array();
+        $variables['header'][$row_key][$col_key]['tag'] = $is_header ? 'th' : 'td';
+        $variables['header'][$row_key][$col_key]['attributes'] = $cell_attributes;
+        $variables['header'][$row_key][$col_key]['content'] = $cell_content;
       }
-      $variables['header'][$col_key] = array();
-      $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td';
-      $variables['header'][$col_key]['attributes'] = $cell_attributes;
-      $variables['header'][$col_key]['content'] = $cell_content;
+      $header_columns = max($header_columns_in_row, $header_columns);
+    }
+
+    // Get single row header back to normal for BC.
+    //// @todo: remove this in D9.
+    if (empty($variables['header_multilevel'])) {
+      $variables['header'] = $variables['header']['single_row'];
+    } else {
+      $variables['header_multilevel'] = $variables['header'];
     }
   }
   $variables['header_columns'] = $header_columns;
@@ -1765,7 +1787,7 @@ function drupal_common_theme() {
       'variables' => array('links' => array()),
     ),
     'table' => array(
-      'variables' => array('header' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''),
+      'variables' => array('header' => NULL, 'header_multilevel' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''),
     ),
     'tablesort_indicator' => array(
       'variables' => array('style' => NULL),
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
index 2da6ef9..e5d5f7b 100644
--- a/core/lib/Drupal/Core/Render/Element/Table.php
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -60,6 +60,7 @@ public function getInfo() {
     $class = get_class($this);
     return array(
       '#header' => array(),
+      '#header_multilevel' => FALSE,
       '#rows' => array(),
       '#empty' => '',
       // Properties for tableselect support.
diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig
index 4436570..82ae0a0 100644
--- a/core/modules/system/templates/table.html.twig
+++ b/core/modules/system/templates/table.html.twig
@@ -8,10 +8,8 @@
  * - caption: A localized string for the <caption> tag.
  * - colgroups: Column groups. Each group contains the following properties:
  *   - attributes: HTML attributes to apply to the <col> tag.
- *     Note: Drupal currently supports only one table header row, see
- *     https://www.drupal.org/node/893530 and
- *     http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
- * - header: Table header cells. Each cell contains the following properties:
+ * - header: Table header rows. Each row contains and array of cell with the
+ *     following properties:
  *   - tag: The HTML tag name to use; either 'th' or 'td'.
  *   - attributes: HTML attributes to apply to the tag.
  *   - content: A localized string for the title of the column.
@@ -60,13 +58,24 @@
 
   {% if header %}
     <thead>
-      <tr>
-        {% for cell in header %}
-          <{{ cell.tag }}{{ cell.attributes }}>
-            {{- cell.content -}}
-          </{{ cell.tag }}>
-        {% endfor %}
-      </tr>
+      {% for row in header if header_multilevel %}
+        <tr>
+          {% for cell in row %}
+            <{{ cell.tag }}{{ cell.attributes }}>
+              {{- cell.content -}}
+            </{{ cell.tag }}>
+          {% endfor %}
+        </tr>
+      {% endfor %}
+      {% if header_multilevel == FALSE %}
+        <tr>
+          {% for cell in header %}
+            <{{ cell.tag }}{{ cell.attributes }}>
+              {{- cell.content -}}
+            </{{ cell.tag }}>
+          {% endfor %}
+        </tr>
+      {% endif %}
     </thead>
   {% endif %}
 
diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php
index 34cbfc0..d056c7d 100644
--- a/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php
@@ -308,4 +308,33 @@ public function testThemeTableTitle() {
     $this->assertRaw('Update my favourite fruit is <strong>bananas</strong>');
   }
 
+  /**
+   * Tests multi rows header table.
+   */
+  public function testThemeTableHeaderMultiRows() {
+    $table = [
+      '#type' => 'table',
+      '#header' => [
+        'row1' => [
+          'bigcell' => [
+            'data' => 'Big header',
+            'rowspan' => 2
+          ],
+          'longcell' => [
+            'data' => 'Long header',
+            'colspan' => 2
+          ],
+          'Small header'
+        ],
+        'row2' => ['cell1', 'cell2', 'cell3']
+      ],
+      '#header_multilevel' => TRUE,
+      '#empty' => 'There are no lines here.',
+    ];
+    $this->render($table);
+    $this->removeWhiteSpace();
+    $this->assertRaw('<thead><tr><th rowspan="2">Big header</th><th colspan="2">Long header</th><th>Small header</th></tr><tr><th>cell1</th><th>cell2</th><th>cell3</th></tr></thead>', 'Table header found.');
+    $this->assertRaw('<tbody><tr><td colspan="4">There are no lines here.</td></tr></tbody>', 'Table empty message found.');
+  }
+
 }
diff --git a/core/themes/classy/templates/dataset/table.html.twig b/core/themes/classy/templates/dataset/table.html.twig
index 2afa9c1..e36f955 100644
--- a/core/themes/classy/templates/dataset/table.html.twig
+++ b/core/themes/classy/templates/dataset/table.html.twig
@@ -8,10 +8,8 @@
  * - caption: A localized string for the <caption> tag.
  * - colgroups: Column groups. Each group contains the following properties:
  *   - attributes: HTML attributes to apply to the <col> tag.
- *     Note: Drupal currently supports only one table header row, see
- *     https://www.drupal.org/node/893530 and
- *     http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
- * - header: Table header cells. Each cell contains the following properties:
+ * - header: Table header rows. Each row contains and array of cell with the
+ *     following properties:
  *   - tag: The HTML tag name to use; either 'th' or 'td'.
  *   - attributes: HTML attributes to apply to the tag.
  *   - content: A localized string for the title of the column.
@@ -58,18 +56,34 @@
 
   {% if header %}
     <thead>
+    {% for row in header if header_multilevel %}
       <tr>
-        {% for cell in header %}
+        {% for cell in row %}
           {%
             set cell_classes = [
-              cell.active_table_sort ? 'is-active',
-            ]
+            cell.active_table_sort ? 'is-active',
+          ]
           %}
           <{{ cell.tag }}{{ cell.attributes.addClass(cell_classes) }}>
             {{- cell.content -}}
           </{{ cell.tag }}>
         {% endfor %}
       </tr>
+    {% endfor %}
+    {% if header_multilevel == FALSE %}
+      <tr>
+      {% for cell in header %}
+        {%
+          set cell_classes = [
+            cell.active_table_sort ? 'is-active',
+          ]
+        %}
+        <{{ cell.tag }}{{ cell.attributes.addClass(cell_classes) }}>
+          {{- cell.content -}}
+        </{{ cell.tag }}>
+      {% endfor %}
+      </tr>
+    {% endif %}
     </thead>
   {% endif %}
 
diff --git a/core/themes/stable/templates/dataset/table.html.twig b/core/themes/stable/templates/dataset/table.html.twig
index 231ada2..9665bd8 100644
--- a/core/themes/stable/templates/dataset/table.html.twig
+++ b/core/themes/stable/templates/dataset/table.html.twig
@@ -8,10 +8,8 @@
  * - caption: A localized string for the <caption> tag.
  * - colgroups: Column groups. Each group contains the following properties:
  *   - attributes: HTML attributes to apply to the <col> tag.
- *     Note: Drupal currently supports only one table header row, see
- *     https://www.drupal.org/node/893530 and
- *     http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
- * - header: Table header cells. Each cell contains the following properties:
+ * - header: Table header rows. Each row contains and array of cell with the
+ *     following properties:
  *   - tag: The HTML tag name to use; either 'th' or 'td'.
  *   - attributes: HTML attributes to apply to the tag.
  *   - content: A localized string for the title of the column.
@@ -58,13 +56,24 @@
 
   {% if header %}
     <thead>
-      <tr>
-        {% for cell in header %}
-          <{{ cell.tag }}{{ cell.attributes }}>
-            {{- cell.content -}}
-          </{{ cell.tag }}>
-        {% endfor %}
-      </tr>
+      {% for row in header if header_multilevel %}
+        <tr>
+          {% for cell in row %}
+            <{{ cell.tag }}{{ cell.attributes }}>
+              {{- cell.content -}}
+            </{{ cell.tag }}>
+          {% endfor %}
+        </tr>
+      {% endfor %}
+      {%if header_multilevel == FALSE %}
+        <tr>
+          {% for cell in header %}
+            <{{ cell.tag }}{{ cell.attributes }}>
+              {{- cell.content -}}
+            </{{ cell.tag }}>
+          {% endfor %}
+        </tr>
+      {% endfor %}
     </thead>
   {% endif %}
 
