Sticky Headers and Sidebars
position: sticky for headers and side rails — containing blocks, overflow traps, top offsets, and stacking with page chrome.
- css
- sticky
- position
position: sticky is hybrid positioning: the element flows normally until it would cross a threshold (top, bottom, inset-block-start, …), then sticks within its containing block. Perfect for table headers, section labels, and side-of-article TOCs — when overflow ancestors don’t sabotage it.
Docs: MDN position, sticky positioning.
Basic sticky header
.site-header {
position: sticky;
top: 0;
z-index: 200;
background: color-mix(in srgb, var(--surface) 92%, transparent);
backdrop-filter: blur(10px);
}
Without a background, content scrolling underneath makes text unreadable. Prefer solid or frosted backgrounds.
Sticky sidebar
.layout {
display: grid;
grid-template-columns: 1fr 16rem;
gap: 2rem;
align-items: start; /* important so sticky child can move */
}
.toc {
position: sticky;
top: 5rem; /* clear the fixed/sticky site header */
max-height: calc(100dvh - 6rem);
overflow: auto;
}
align-items: stretch (grid default) can make the sidebar as tall as the main column, so sticky has no room to travel. align-items: start sizes the sidebar to its content height.
The overflow trap
Any ancestor with overflow: hidden | auto | scroll (other than visible) can become the scrollport that sticky sticks within — or prevent sticking as expected.
/* Common bug */
.page {
overflow-x: hidden; /* creates a scroll containment side effect */
}
.page .header {
position: sticky;
top: 0; /* may stick inside .page, not the viewport */
}
Debug by walking ancestors in DevTools and checking computed overflow.
Sticky table headers
.table-wrap {
max-height: 24rem;
overflow: auto;
}
th {
position: sticky;
top: 0;
background: var(--surface);
z-index: 1;
}
Sticky th sticks within .table-wrap, which is correct for scrollable tables.
Stacking and dual chrome
:root {
--header-h: 3.5rem;
}
.site-header {
position: sticky;
top: 0;
height: var(--header-h);
z-index: 30;
}
.section-label {
position: sticky;
top: var(--header-h);
z-index: 20;
background: var(--surface);
}
Offset nested sticky labels by the height of the bar above them.
Accessibility
- Don’t trap focus inside sticky regions incorrectly.
- Ensure sticky headers don’t hide focused elements — use
scroll-padding-topon the scroll container:
html {
scroll-padding-top: var(--header-h);
}
Interview out-loud
“Sticky elements flow until they hit top/bottom thresholds, then stick inside their containing block. Overflow on ancestors is the usual reason sticky ‘doesn’t work.’ For sidebars I set align-items: start on the grid, offset top for existing headers, and set scroll-padding so in-page anchors aren’t covered.”
Footguns
- Parent
overflow: hiddenfor rounded corners killing sticky. - No background on sticky header.
- Sticky competing z-index with modals.
- Using sticky when
fixedis required (always viewport-relative regardless of parent height). - Forgetting mobile address-bar
100vhissues for sticky max-heights — preferdvh.
Sticky column in tables
.matrix {
overflow: auto;
max-width: 100%;
}
.matrix th.sticky-col,
.matrix td.sticky-col {
position: sticky;
inset-inline-start: 0;
background: var(--surface);
z-index: 2;
}
.matrix thead th {
position: sticky;
top: 0;
z-index: 3;
}
.matrix thead th.sticky-col {
z-index: 4; /* corner cell above both */
}
Corner cells need the highest z-index so row/column sticky edges do not paint over labels. Backgrounds are mandatory — transparent sticky cells show scrolling data underneath.
Related
- Position static relative absolute fixed sticky
- Stacking contexts and z-index
- Overflow scroll and scroll snap
- Grid template areas
Further reading
Related guides
- Position: static, relative, absolute, fixed, stickyContaining blocks, stacking, sticky constraints, and when position is the right tool vs flex/grid.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.