How to keep a table header fixed while scrolling
Short answer
Give the header cells position: sticky and top: 0, and put the table in a block with a limited height and scrolling:
<style>
.wrap { max-height: 150px; overflow: auto; border: 1px solid silver; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 6px 12px; text-align: left; }
th { position: sticky; top: 0; background: steelblue; color: white; }
</style>
<div class="wrap">
<table>
<thead><tr><th>Day</th><th>Visitors</th></tr></thead>
<tbody>
<tr><td>Monday</td><td>120</td></tr><tr><td>Tuesday</td><td>98</td></tr>
<tr><td>Wednesday</td><td>143</td></tr><tr><td>Thursday</td><td>110</td></tr>
<tr><td>Friday</td><td>87</td></tr><tr><td>Saturday</td><td>64</td></tr>
</tbody>
</table>
</div>Scroll the table — the header row stays at the top.
Scrolling the whole page
If the table scrolls with the page, no wrapper is needed: the header sticks to the top of the window. When the site has a fixed header, set top to its height, for example top: 60px.
Fixing the first column
The first column is fixed the same way for horizontal scrolling: its cells get position: sticky; left: 0 and a background.
Common mistake
The sticky cells are left without a background. The header is transparent, and while scrolling the table rows show through it, overlapping the headings. Another trap is overflow: hidden on some parent between the table and the scrolling block: sticky then stops working.
Functions used in this recipe
- position — How an element is positioned: in the normal flow or by offsets.
- top, right, bottom, left — The offsets of a positioned element from the edges of its reference box: top, right, bottom and left.
- <thead>, <tbody>, <tfoot> — Groups of table rows: the head, the body and the footer.
- <table> — A table made of rows (`<tr>`) and cells.
- overflow — What to do with content that does not fit into the element.