diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc
index cd5c0c1..9ab1789 100644
--- a/core/includes/tablesort.inc
+++ b/core/includes/tablesort.inc
@@ -42,15 +42,15 @@ function tablesort_init($header) {
  */
 function tablesort_header($cell, $header, $ts) {
   // Special formatting for the currently sorted column header.
-  if (is_array($cell) && isset($cell['field'])) {
+  if (isset($cell['field'])) {
     $title = t('sort by @s', array('@s' => $cell['data']));
     if ($cell['data'] == $ts['name']) {
       // aria-sort is a WAI-ARIA property that indicates if items in a table
       // or grid are sorted in ascending or descending order. See
       // http://www.w3.org/TR/wai-aria/states_and_properties#aria-sort
-      $cell['aria-sort'] = ($ts['sort'] == 'asc') ? 'ascending' : 'descending';
+      $cell['attributes']['aria-sort'] = ($ts['sort'] == 'asc') ? 'ascending' : 'descending';
       $ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
-      $cell['class'][] = 'active';
+      $cell['attributes']['class'][] = 'active';
       $tablesort_indicator = array(
         '#theme' => 'tablesort_indicator',
         '#style' => $ts['sort'],
@@ -70,35 +70,6 @@ function tablesort_header($cell, $header, $ts) {
 }
 
 /**
- * Formats a table cell.
- *
- * Adds a class attribute to all cells in the currently active column.
- *
- * @param $cell
- *   The cell to format.
- * @param $header
- *   An array of column headers in the format described in '#type' => 'table'.
- * @param $ts
- *   The current table sort context as returned from tablesort_init().
- * @param $i
- *   The index of the cell's table column.
- *
- * @return
- *   A properly formatted cell, ready for _theme_table_cell().
- */
-function tablesort_cell($cell, $header, $ts, $i) {
-  if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) {
-    if (is_array($cell)) {
-      $cell['class'][] = 'active';
-    }
-    else {
-      $cell = array('data' => $cell, 'class' => array('active'));
-    }
-  }
-  return $cell;
-}
-
-/**
  * Composes a URL query parameter array for table sorting links.
  *
  * @return
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index f98df80..eaf26ad 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1537,9 +1537,18 @@ function theme_table($variables) {
     // HTML requires that the thead tag has tr tags in it followed by tbody
     // tags. Using ternary operator to check and see if we have any rows.
     $output .= (count($rows) ? ' <thead><tr>' : ' <tr>');
-    $i = 0;
-    foreach ($header as $cell) {
-      $i++;
+    foreach ($header as $i => $cell) {
+      // If the cell is not an array, make it one.
+      if (!is_array($cell)) {
+        $cell = array('data' => $cell);
+      }
+      $cell['data'] = isset($cell['data']) ? $cell['data'] : '';
+      $cell['attributes'] = isset($cell['attributes']) ? $cell['data'] : array();
+
+      // 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
@@ -1553,7 +1562,20 @@ function theme_table($variables) {
         }
       }
       $cell = tablesort_header($cell, $header, $ts);
-      $output .= _theme_table_cell($cell, TRUE);
+
+      // Move cell properties that are not data or attributes into attributes.
+      // Doing this after tablesort_header because it removes the sort and field
+      // keys.
+      foreach ($cell as $key => $value) {
+        if ($key != 'data' && $key != 'attributes') {
+          $cell['attributes'][$key] = $value;
+        }
+      }
+      if (is_array($cell['data'])) {
+        $cell['data'] = drupal_render($cell['data']);
+      }
+      $cell_tag = ($is_header) ? 'th' : 'td';
+      $output .= '<' . $cell_tag . new Attribute($cell['attributes']) . '>' . $cell['data'] . '</' . $cell_tag . '>';
     }
     // Using ternary operator to close the tags based on whether or not there are rows
     $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n");
@@ -1595,19 +1617,39 @@ function theme_table($variables) {
         $i = 0;
         foreach ($cells as $cell) {
           $i++;
+          // If the cell is not an array, make it one.
+          if (!is_array($cell)) {
+            $cell = array('data' => $cell);
+          }
+          $cell['data'] = isset($cell['data']) ? $cell['data'] : '';
+          $cell['attributes'] = isset($cell['attributes']) ? $cell['data'] : array();
+
+          // Flag the cell as a header or not and remove the flag.
+          $is_header = !empty($cell['header']);
+          unset($cell['header']);
+
+          // Move cell properties that are not data or attributes
+          // into attributes.
+          foreach ($cell as $key => $value) {
+            if ($key != 'data' && $key != 'attributes') {
+              $cell['attributes'][$key] = $value;
+            }
+          }
+
           // Add active class if needed for sortable tables.
-          $cell = tablesort_cell($cell, $header, $ts, $i);
+          if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) {
+            $cell['attributes']['class'][] = 'active';
+          }
           // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
           // class from header to cell as needed.
           if (isset($responsive[$i])) {
-            if (is_array($cell)) {
-              $cell['class'][] = $responsive[$i];
-            }
-            else {
-              $cell = array('data' => $cell, 'class' => $responsive[$i]);
-            }
+            $cell['attributes']['class'][] = $responsive[$i];
+          }
+          if (is_array($cell['data'])) {
+            $cell['data'] = drupal_render($cell['data']);
           }
-          $output .= _theme_table_cell($cell);
+          $cell_tag = ($is_header) ? 'th' : 'td';
+          $output .= '<' . $cell_tag . new Attribute($cell['attributes']) . '>' . $cell['data'] . '</' . $cell_tag . '>';
         }
         $output .= " </tr>\n";
       }
@@ -1792,47 +1834,6 @@ function template_preprocess_container(&$variables) {
  */
 
 /**
- * Returns HTML output for a single table cell for theme_table().
- *
- * @param $cell
- *   Array of cell information, or string to display in cell.
- * @param bool $header
- *   TRUE if this cell is a table header cell, FALSE if it is an ordinary
- *   table cell. If $cell is an array with element 'header' set to TRUE, that
- *   will override the $header parameter.
- *
- * @return
- *   HTML for the cell.
- */
-function _theme_table_cell($cell, $header = FALSE) {
-  $attributes = '';
-
-  if (is_array($cell)) {
-    $data = isset($cell['data']) ? $cell['data'] : '';
-    // Cell's data property can be a string or a renderable array.
-    if (is_array($data)) {
-      $data = drupal_render($data);
-    }
-    $header |= isset($cell['header']);
-    unset($cell['data']);
-    unset($cell['header']);
-    $attributes = new Attribute($cell);
-  }
-  else {
-    $data = $cell;
-  }
-
-  if ($header) {
-    $output = "<th$attributes>$data</th>";
-  }
-  else {
-    $output = "<td$attributes>$data</td>";
-  }
-
-  return $output;
-}
-
-/**
  * Adds a default set of helper variables for preprocessors and templates.
  *
  * This function is called for theme hooks implemented as templates only, not
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
index a5502e7..2376399 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
@@ -106,4 +106,24 @@ function testThemeTableWithNoStriping() {
     $this->assertNoRaw('class="odd"', 'Odd/even classes were not added because $no_striping = TRUE.');
     $this->assertNoRaw('no_striping', 'No invalid no_striping HTML attribute was printed.');
   }
+
+  /**
+   * Tests that the 'header' option in cells works correctly.
+   */
+  function testThemeTableHeaderCellOption() {
+    $rows = array(
+      array(
+        array('data' => 1, 'header' => TRUE),
+        array('data' => 1, 'header' => FALSE),
+        array('data' => 1),
+      ),
+    );
+    $table = array(
+      '#type' => 'table',
+      '#rows' => $rows,
+    );
+    $this->content = drupal_render($table);
+    $this->assertRaw('<th>1</th><td>1</td><td>1</td>', 'The th and td tags was printed correctly.');
+  }
+
 }
