Skip to main content

Fixed "Sticky" Table Header: Advanced Table Design

To create an accessible table with a "sticky" header, use CSS to fix the header's position within a single, semantically correct HTML table.

Warning: A common but inaccessible practice is to split the content into two separate tables—one for the header and one for the data. This approach must be avoided, as it severs the programmatic link between header and data cells, making the table unusable for screen reader users.

CSS Table with sticky header
Col 1Col 2Col 3Col 4
Row Header 1Data 1Data 2Data 3
Row Header 2Data 1Data 2Data 3
Row Header 3Data 1Data 2Data 3
Row Header 4Data 1Data 2Data 3
Row Header 5Data 1Data 2Data 3
Row Header 6Data 1Data 2Data 3
Row Header 7Data 1Data 2Data 3
Row Header 8Data 1Data 2Data 3
Row Header 9Data 1Data 2Data 3
Row Header 10Data 1Data 2Data 3
Row Header 11Data 1Data 2Data 3
Row Header 12Data 1Data 2Data 3
Row Header 13Data 1Data 2Data 3
Row Header 14Data 1Data 2Data 3
Row Header 15Data 1Data 2Data 3
Row Header 16Data 1Data 2Data 3
Row Header 17Data 1Data 2Data 3
Row Header 18Data 1Data 2Data 3
Row Header 19Data 1Data 2Data 3
Row Header 20Data 1Data 2Data 3
Row Header 21Data 1Data 2Data 3
Row Header 22Data 1Data 2Data 3
Row Header 23Data 1Data 2Data 3
Row Header 24Data 1Data 2Data 3

Code

The recommended technique for creating an accessible sticky header relies on CSS. A container <div> is styled with an overflow property to create a scrollable viewport. Inside this container, the table's header cells (<th> within <thead>) are assigned position: sticky.

For full browser compatibility, particularly for keyboard navigation in Chrome, the <table> element must have the tabindex="0" attribute.

<div class="fixTableHead">
   <table tabindex="0">
      <caption class="sr-only">CSS Table with sticky header</caption>
      <thead>
         <tr>
            <th scope="col">Col 1</th>
            <th scope="col">Col 2</th>
            <th scope="col">Col 3</th>
            <th scope="col">Col 4</th>
         </tr>
      </thead>
      <tbody>
         <tr>
            <th scope="row">Row Header 1</th>
            <td>Data 1</td>
            <td>Data 2</td>
            <td>Data 3</td>
         </tr>
        <!-- Insert More Rows Here -->
      </tbody>
   </table>
</div>

CSS Implementation

The corresponding CSS is straightforward and consists of three rules:

  1. Container (.fixTableHead): This rule defines a fixed height for the wrapper div and sets overflow-y: auto to enable a vertical scrollbar when the content exceeds the container's height.
  2. Sticky Header (thead th): This rule applies position: sticky and top: 0 to the header cells, "sticking" them to the top of the container during scrolling.
  3. Focus Style (table:focus): This rule adds a visible focus indicator to the table itself. This is essential for keyboard users to know their location when they navigate to the scrollable table area.
.fixTableHead {
   overflow-y: auto;
   height: 15em;
}

.fixTableHead thead th {
   position: sticky;
   top: 0;
}

table:focus {
   border: #f00 solid 2px !important;
}

While other variations exist, this CSS-based approach is a simple and reliable method for creating accessible sticky table headers.

Last modified