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.
| Col 1 | Col 2 | Col 3 | Col 4 |
|---|---|---|---|
| Row Header 1 | Data 1 | Data 2 | Data 3 |
| Row Header 2 | Data 1 | Data 2 | Data 3 |
| Row Header 3 | Data 1 | Data 2 | Data 3 |
| Row Header 4 | Data 1 | Data 2 | Data 3 |
| Row Header 5 | Data 1 | Data 2 | Data 3 |
| Row Header 6 | Data 1 | Data 2 | Data 3 |
| Row Header 7 | Data 1 | Data 2 | Data 3 |
| Row Header 8 | Data 1 | Data 2 | Data 3 |
| Row Header 9 | Data 1 | Data 2 | Data 3 |
| Row Header 10 | Data 1 | Data 2 | Data 3 |
| Row Header 11 | Data 1 | Data 2 | Data 3 |
| Row Header 12 | Data 1 | Data 2 | Data 3 |
| Row Header 13 | Data 1 | Data 2 | Data 3 |
| Row Header 14 | Data 1 | Data 2 | Data 3 |
| Row Header 15 | Data 1 | Data 2 | Data 3 |
| Row Header 16 | Data 1 | Data 2 | Data 3 |
| Row Header 17 | Data 1 | Data 2 | Data 3 |
| Row Header 18 | Data 1 | Data 2 | Data 3 |
| Row Header 19 | Data 1 | Data 2 | Data 3 |
| Row Header 20 | Data 1 | Data 2 | Data 3 |
| Row Header 21 | Data 1 | Data 2 | Data 3 |
| Row Header 22 | Data 1 | Data 2 | Data 3 |
| Row Header 23 | Data 1 | Data 2 | Data 3 |
| Row Header 24 | Data 1 | Data 2 | Data 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:
- Container (.fixTableHead): This rule defines a fixed
heightfor the wrapperdivand setsoverflow-y: autoto enable a vertical scrollbar when the content exceeds the container's height. - Sticky Header (thead th): This rule applies
position: stickyandtop: 0to the header cells, "sticking" them to the top of the container during scrolling. - 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.
