A table header that stays put
| Region | Sessions |
|---|---|
| Auckland | 4 812 |
| Bergen | 3 190 |
| Cádiz | 2 774 |
| Dakar | 2 015 |
| Esbjerg | 1 640 |
| Faro | 1 288 |
| Galway | 1 004 |
CaveatAny ancestor carrying overflow: hidden switches sticky off silently — no warning, no error, the header just scrolls away. That ancestor is usually a card wrapper somebody added months later.
FallbackWhere sticky is unsupported the header scrolls with the table, which is the plain table you started with. Nothing to polyfill.
/* sticky goes on th, not thead */
.scroller { overflow: auto; }
.scroller th {
position: sticky;
top: 0;
/* or rows show through */
background: #1a212d;
}
Published
Questions
Why does position: sticky not work on thead?
Sticky on <thead> and <tr> was ignored for years and still behaves differently between engines, so put position: sticky and top: 0 on each <th> instead. The cells then stick one by one and look like a single header row.
Why do the borders of my sticky header scroll away?
With border-collapse: collapse the borders belong to the table, not to the sticky cell, so they stay behind while the <th> moves. Use border-collapse: separate with border-spacing: 0, or draw the line with a box-shadow on the cell.
Can a table header stick inside a scrolling div?
Yes, and that is the usual setup: the header sticks to the nearest scrolling ancestor, a wrapper with overflow: auto and a max-height. Without a height limit the wrapper never scrolls, and an overflow: hidden ancestor in between switches sticky off.