6 Pure CSS1 with JSPublished 2026-10-05Free · no sign-up
Seven uses of position: sticky: a header that stays at the top, a table of contents beside an article, table headings that stay visible, contact-list letters that push each other away, a buy bar pinned to the bottom, a first column pinned in a wide table and a shadow that appears only when the header is stuck.
position: sticky with top: 0 keeps the header at the top of the scrolling area once you scroll past it, and it stays in normal flow, so nothing jumps and no placeholder is needed. A solid background stops content showing through it.
A table of contents that stays in view while the article beside it scrolls. The trick is align-self: start on the sticky column; in a grid row the item would otherwise stretch to the full height and have nowhere to stick.
View code
<div class="st2" tabindex="0" role="region" aria-label="Article, scroll"><div class="g"><nav aria-label="On this page"><a href="#a">Setup</a><a href="#b">Usage</a><a href="#c">FAQ</a></nav><article><h3 id="a">Setup</h3><p>Install, then import the styles.</p><h3 id="b">Usage</h3><p>Add the class to any list.</p><p>It works with nested lists too.</p><h3 id="c">FAQ</h3><p>Yes, it supports right-to-left text.</p><p>No, it has no dependencies.</p></article></div></div>
Long data tables keep their column headings visible by making the th cells sticky inside a scrolling wrapper. Sticky works on th, not on thead in every browser, and border-collapse: separate keeps the header borders from scrolling away.
An alphabetical contact list where each letter heading sticks to the top until the next letter pushes it away, because each heading is sticky only within its own section. It is the pattern phone contact lists use.
On a long product page, the price and Add to cart button stick to the bottom of the view once the main button has scrolled away. bottom: 0 on a sticky element at the end of the content does it in CSS, with no scroll listener.
View code
<div class="st5" tabindex="0" role="region" aria-label="Product page, scroll"><h3>Linen throw</h3><p>Stonewashed Belgian linen, 130 × 170 cm.</p><p>Machine washable at 40°C.</p><p>Gets softer with every wash.</p><p>Made in a family mill in Kortrijk.</p><p>Ships in recycled packaging.</p><div class="buy"><b>$89</b><button>Add to cart</button></div></div>
When a comparison table scrolls sideways, its first column stays pinned with position: sticky and left: 0, so you always know which row you are reading. The pinned cells need a background colour or the scrolled cells show through.
CSS cannot yet tell when a sticky element is stuck, so a one-pixel sentinel just above the header is watched by an IntersectionObserver; when it scrolls out of view the header gets a class that adds a shadow. The shadow appears exactly when the header starts floating.
var box = document.querySelector('.st7'), h = box.querySelector('header');
new IntersectionObserver(function (es) { h.classList.toggle('stuck', !es[0].isIntersecting); }, { root: box }).observe(box.querySelector('.sent'));
About these CSS Sticky Elements
Sticky needs an offset
position: sticky does nothing until you give it top, bottom, left or right. The element then sticks when it reaches that distance from its scrolling container's edge.
Watch the ancestors
Sticky stops working if any ancestor between it and the scroller has overflow: hidden or auto, because that ancestor becomes the scroll container. Check those first when sticky seems broken.
Room to move
A sticky element can only travel within its parent. In a grid or flex row, give it align-self: start, otherwise it stretches to the parent's full height and has nowhere to go.
Backgrounds and stacking
Sticky elements overlap the content scrolling beneath them, so give them a background colour, and a z-index if positioned content would otherwise pass over them.
Frequently asked questions
Why does my sticky table header not stick?
Make the th cells sticky rather than the thead, and use border-collapse: separate.
Can CSS detect when an element is stuck?
Not yet in all browsers. The last demo uses an IntersectionObserver sentinel, which works everywhere.
Does sticky hurt scrolling performance?
No. It is handled by the browser's compositor like normal scrolling.