Fixed Table Headers with Pure CSS: Sticky On Scroll

Standard HTML tables are not very complex, and generally do not need to be. However, there may come a scenario where you have large amounts of data or very little space to display the data.

In these cases, fixed table headers that are sticky on scroll can be a perfect solution! Luckily, there are a few simple and useful ways to accomplish this with pure CSS. In this article, we'll walk through applying fixed headers and sidebars to your tables with examples.

The Table Data

First, we'll start with creating some table data that we can display on the screen. We'll use a somewhat large amount of data to illustrate the use of a sticky header in just a moment:

<table>
<thead>
<tr>
<th>Artist</th>
<th>Album</th>
<th>Song</th>
<th>Date</th>
</tr>
</thead>

<tbody>
<tr>
<td>B.B. King</td>
<td>Completely Well</td>
<td>The Thrill Is Gone</td>
<td>1969</td>
</tr>
<tr>
<td>Freddie King</td>
<td>Getting Ready... (World)</td>
<td>Going Down</td>
<td>1971</td>
</tr>
<tr>
<td>Allman Brothers Band</td>
<td>The Allman Brothers Band</td>
<td>Trouble No More</td>
<td>1969</td>
</tr>
<tr>
<td>Jimi Hendrix</td>
<td>Are You Experienced</td>
<td>Purple Haze</td>
<td>1967</td>
</tr>
<tr>
<td>Buddy Guy</td>
<td>I Was Walkin' Through The Woods</td>
<td>Stone Crazy</td>
<td>1986</td>
</tr>
</tbody>
</table>

Fixed Table Headers at the Page Level

When creating fixed table headers at the page level, you're ensuring that whenever any part of your table is in the viewport, its header row is also visible to the user.

This is easily accomplished with just a few lines of CSS code:

th {
position: sticky;
position: -webkit-sticky;
top: 0px;
z-index: 2;
}

Artist Album Song Date
B.B. King Completely Well The Thrill Is Gone 1969
Freddie King Getting Ready... (World) Going Down 1971
Allman Brothers Band The Allman Brothers Band Trouble No More 1969
Jimi Hendrix Are You Experienced Purple Haze 1967
Buddy Guy I Was Walkin' Through The Woods Stone Crazy 1986

As you scroll up and down the page, you'll notice the behavior of the table column headers are different as parts of the table start to fall out of view. This ensures that no matter which portion of the data you're looking at, you always have the header indicators to reference and identify each of the columns.

The top property value of 0px ensures that the table header always remains at the very top of the viewable area, and the z-index value of 2 ensures that the table header always remains a layer above its data so it's always visible and never covered.

Fixed Row Headers

By default, the web browser will assume that the fixed items in question will the table column headers. Similarly to fixed column headers, you can also create sticky row headers by adding in the scope selector:

th[scope="row"] {
position: sticky;
position: -webkit-sticky;
left: 0px;
z-index: 2;
}

You'll also need to ensure that your th tags change from the top table row to only the first column in each row:

<table>
<thead>
<tr>
<th>Artist</th>
<td>Album</td>
<td>Song</td>
<td>Release Date</td>
</tr>
</thead>

<tbody>
<tr>
<th>B.B. King</th>
<td>Completely Well</td>
<td>The Thrill Is Gone</td>
<td>1969</td>
</tr>
<tr>
<th>Freddie King</th>
<td>Getting Ready... (World)</td>
<td>Going Down</td>
<td>1971</td>
</tr>
<tr>
<th>Allman Brothers Band</th>
<td>The Allman Brothers Band</td>
<td>Trouble No More</td>
<td>1969</td>
</tr>
<tr>
<th>Jimi Hendrix</th>
<td>Are You Experienced</td>
<td>Purple Haze</td>
<td>1967</td>
</tr>
<tr>
<th>Buddy Guy</th>
<td>I Was Walkin' Through The Woods</td>
<td>Stone Crazy</td>
<td>1986</td>
</tr>
</tbody>
</table>

Artist Album Song Release Date
B.B. King Completely Well The Thrill Is Gone 1969
Freddie King Getting Ready... (World) Going Down 1971
Allman Brothers Band The Allman Brothers Band Trouble No More 1969
Jimi Hendrix Are You Experienced Purple Haze 1967
Buddy Guy I Was Walkin' Through The Woods Stone Crazy 1986

Column headers will do just fine without defining a scope as they are assumed to be the default.

Other Useful Tidbits

Here is some important information you'll need to remember for cross-browser compatibility:

  • Both horizontal and vertical scroll will allow you to better fit tables within a responsive layout.  So, if you have super wide tables that look great on desktop but not on mobile, use the above tips to add a horizontal scroll to your table with fixed row headers.
  • The caption element will not stick in place within a table scroll. As you scroll left to right, it will disappear and reappear.
  • Firefox is the only known browser at the time of this writing that supports position: sticky on the thead element. For this reason, we are assigning it to the th elements as a workaround.
  • Safari on MacOS and iOS requires the use of -webkit-sticky for the position property.
  • There is no support for fixed table headers in any version of Internet Explorer. Time to upgrade!

Conclusion

That's it for fixed table headers and responsive tables! It's a very simple hack to get the result you're looking for, but can be highly beneficial to your users in cases where you are displaying large amounts of data.

Make sure to test this feature thoroughly across multiple devices and web browsers to ensure you're getting the display results you need.

Created: April 24, 2021