CSS Sticky Header with position: sticky

Sticky headers without JavaScript scroll listeners

The old way used JavaScript scroll events and getBoundingClientRect to toggle a class. Sticky positioning does it in one property.

Old way 15+ lines
// JavaScript: scroll listenerwindow.addEventListener('scroll', () => {  const rect = header.getBoundingClientRect();  if (rect.top <= 0)    header.classList.add('fixed');  else    header.classList.remove('fixed');});.header.fixed {  position: fixed;  top: 0;}
3 lines
.header   {  position: sticky;  top: 0;}
Widely available Since 2022 96% global usage

This feature is well established and works across many devices and browser versions. It has been available across browsers since 2022.

Safe to use without fallbacks.

56+
32+
13+
16+
position: sticky
📌 I'm a sticky header
List item 1
List item 2
List item 3
List item 4
List item 5
List item 6
List item 7
List item 8
List item 9
List item 10
List item 11
List item 12

No JavaScript

The browser handles scroll. No listeners, no getBoundingClientRect, no class toggles.

Respects flow

Sticky stays in layout until it hits the threshold, then sticks. No layout jumps.

One property

Set position and top. Works for headers, sidebars, or any element you want to pin.

Lines Saved
15+ → 3
No JS needed
Old Approach
Scroll listener + class
JS + getBoundingClientRect
Modern Approach
One property
position: sticky

How it works

The old way meant a scroll listener, reading getBoundingClientRect on every scroll, and toggling a class to switch between normal and fixed. That's JS, reflows, and extra CSS for the fixed state.

With position: sticky and top: 0, the header stays in flow until it would scroll past the top, then it sticks. The browser does the work. No script, no class, no layout hacks.

ESC