WebCraft

position: sticky

A table header that stays put

RegionSessions
Auckland4 812
Bergen3 190
Cádiz2 774
Dakar2 015
Esbjerg1 640
Faro1 288
Galway1 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.

CSS
/* sticky goes on th, not thead */
.scroller { overflow: auto; }

.scroller th {
  position: sticky;
  top: 0;
  /* or rows show through */
  background: #1a212d;
}

Published

Questions

<details name>

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.