Back to Blog
CSSPositionStickyLayout

CSS Position Sticky Guide

Master CSS position sticky. From basics to common pitfalls to advanced sticky patterns.

B
Bootspring Team
Engineering
September 10, 2020
6 min read

Position sticky combines relative and fixed positioning. Here's how to use it effectively.

Basic Sticky#

1/* Basic sticky element */ 2.sticky-header { 3 position: sticky; 4 top: 0; 5 background: white; 6 z-index: 10; 7} 8 9/* Sticky requires a threshold */ 10.sticky-element { 11 position: sticky; 12 top: 20px; /* Sticks when 20px from top */ 13} 14 15/* Bottom sticky */ 16.sticky-footer { 17 position: sticky; 18 bottom: 0; 19} 20 21/* Side sticky */ 22.sticky-sidebar { 23 position: sticky; 24 top: 100px; 25 align-self: start; /* Important for flex/grid */ 26}

How Sticky Works#

1/* Sticky element behavior: 2 1. Acts like relative until threshold reached 3 2. Acts like fixed while in container bounds 4 3. Stops at container's end */ 5 6.container { 7 /* Sticky element is confined to this */ 8} 9 10.sticky-element { 11 position: sticky; 12 top: 0; 13 /* Scrolls with content until top: 0 reached 14 Then stays fixed until container ends */ 15} 16 17/* Visual example */ 18.scroll-container { 19 height: 300px; 20 overflow-y: auto; 21} 22 23.content-section { 24 min-height: 600px; 25} 26 27.section-header { 28 position: sticky; 29 top: 0; 30 background: #f5f5f5; 31 padding: 1rem; 32}

Common Pitfalls#

1/* PITFALL 1: Parent has overflow */ 2.parent { 3 overflow: hidden; /* Breaks sticky! */ 4 overflow: auto; /* Also breaks sticky! */ 5 overflow: scroll; /* Also breaks sticky! */ 6} 7 8/* FIX: Remove overflow or restructure */ 9.parent { 10 overflow: visible; /* Sticky works */ 11} 12 13/* PITFALL 2: Parent has no height */ 14.parent { 15 /* No height = sticky has no room to stick */ 16} 17 18.child { 19 position: sticky; 20 top: 0; 21 /* Won't work - parent collapses */ 22} 23 24/* FIX: Give parent a height */ 25.parent { 26 min-height: 500px; 27 /* Or let content create height */ 28} 29 30/* PITFALL 3: Missing threshold */ 31.broken { 32 position: sticky; 33 /* No top/bottom/left/right = won't stick */ 34} 35 36/* FIX: Add threshold */ 37.working { 38 position: sticky; 39 top: 0; 40} 41 42/* PITFALL 4: Flex/Grid alignment */ 43.flex-parent { 44 display: flex; 45} 46 47.sticky-child { 48 position: sticky; 49 top: 0; 50 /* Stretched by default, may not work */ 51} 52 53/* FIX: Use align-self */ 54.sticky-child { 55 position: sticky; 56 top: 0; 57 align-self: flex-start; 58}

Sticky Header Patterns#

1/* Basic sticky header */ 2.site-header { 3 position: sticky; 4 top: 0; 5 background: white; 6 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 7 z-index: 100; 8} 9 10/* Sticky header with backdrop blur */ 11.glass-header { 12 position: sticky; 13 top: 0; 14 background: rgba(255, 255, 255, 0.8); 15 backdrop-filter: blur(10px); 16 z-index: 100; 17} 18 19/* Hide on scroll down, show on scroll up */ 20.smart-header { 21 position: sticky; 22 top: 0; 23 transition: transform 0.3s; 24} 25 26.smart-header.hidden { 27 transform: translateY(-100%); 28} 29 30/* Shrinking header */ 31.shrink-header { 32 position: sticky; 33 top: 0; 34 padding: 2rem; 35 transition: padding 0.3s; 36} 37 38.shrink-header.scrolled { 39 padding: 0.5rem; 40}
1/* Sticky sidebar in flex layout */ 2.page-layout { 3 display: flex; 4 gap: 2rem; 5} 6 7.main-content { 8 flex: 1; 9} 10 11.sidebar { 12 width: 300px; 13 align-self: flex-start; /* Critical! */ 14} 15 16.sidebar-content { 17 position: sticky; 18 top: 100px; 19} 20 21/* Sticky sidebar in grid */ 22.grid-layout { 23 display: grid; 24 grid-template-columns: 1fr 300px; 25 gap: 2rem; 26 align-items: start; /* Critical! */ 27} 28 29.grid-sidebar { 30 position: sticky; 31 top: 100px; 32} 33 34/* Sidebar with max height */ 35.tall-sidebar { 36 position: sticky; 37 top: 100px; 38 max-height: calc(100vh - 120px); 39 overflow-y: auto; 40}

Sticky Table Headers#

1/* Sticky table header */ 2.table-container { 3 max-height: 400px; 4 overflow: auto; 5} 6 7table { 8 border-collapse: collapse; 9 width: 100%; 10} 11 12thead th { 13 position: sticky; 14 top: 0; 15 background: #f5f5f5; 16 z-index: 1; 17} 18 19/* Sticky first column */ 20tbody td:first-child, 21thead th:first-child { 22 position: sticky; 23 left: 0; 24 background: white; 25 z-index: 1; 26} 27 28/* Sticky header AND first column */ 29thead th:first-child { 30 position: sticky; 31 top: 0; 32 left: 0; 33 z-index: 2; /* Higher for corner cell */ 34} 35 36/* With shadows for scroll indication */ 37thead th { 38 position: sticky; 39 top: 0; 40 background: white; 41 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 42}

Sticky Section Headers#

1/* Stacking sticky headers */ 2.section { 3 /* Contains the sticky header */ 4} 5 6.section-header { 7 position: sticky; 8 top: 60px; /* Below main header */ 9 background: white; 10 padding: 1rem; 11 border-bottom: 1px solid #eee; 12} 13 14/* Multiple sticky levels */ 15.primary-header { 16 position: sticky; 17 top: 0; 18 z-index: 30; 19} 20 21.secondary-header { 22 position: sticky; 23 top: 60px; /* Height of primary */ 24 z-index: 20; 25} 26 27.tertiary-header { 28 position: sticky; 29 top: 120px; /* Height of primary + secondary */ 30 z-index: 10; 31} 32 33/* Contact list style */ 34.contact-list { 35 /* Natural scroll container */ 36} 37 38.letter-header { 39 position: sticky; 40 top: 0; 41 background: #f0f0f0; 42 padding: 0.5rem 1rem; 43 font-weight: bold; 44} 45 46.contact-item { 47 padding: 1rem; 48 border-bottom: 1px solid #eee; 49}

Sticky with Intersection Observer#

1// Detect when element becomes sticky 2const header = document.querySelector('.sticky-header'); 3const sentinel = document.createElement('div'); 4sentinel.className = 'sticky-sentinel'; 5header.parentElement.insertBefore(sentinel, header); 6 7const observer = new IntersectionObserver( 8 ([entry]) => { 9 header.classList.toggle('is-stuck', !entry.isIntersecting); 10 }, 11 { threshold: 0 } 12); 13 14observer.observe(sentinel);
1/* Style when stuck */ 2.sticky-header { 3 position: sticky; 4 top: 0; 5 transition: box-shadow 0.3s; 6} 7 8.sticky-header.is-stuck { 9 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 10} 11 12/* Sentinel element */ 13.sticky-sentinel { 14 position: absolute; 15 top: -1px; 16 height: 1px; 17 width: 100%; 18}

Responsive Sticky#

1/* Sticky only on desktop */ 2.sidebar { 3 /* Mobile: normal flow */ 4} 5 6@media (min-width: 768px) { 7 .sidebar { 8 position: sticky; 9 top: 100px; 10 } 11} 12 13/* Different positions by breakpoint */ 14.nav { 15 position: sticky; 16 bottom: 0; /* Mobile: bottom nav */ 17} 18 19@media (min-width: 768px) { 20 .nav { 21 position: sticky; 22 top: 0; /* Desktop: top nav */ 23 bottom: auto; 24 } 25}
1/* Sticky footer in viewport */ 2.page { 3 min-height: 100vh; 4 display: flex; 5 flex-direction: column; 6} 7 8.content { 9 flex: 1; 10} 11 12.footer { 13 position: sticky; 14 bottom: 0; 15 background: white; 16 box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); 17} 18 19/* Sticky action bar */ 20.form-container { 21 min-height: 100vh; 22} 23 24.action-bar { 25 position: sticky; 26 bottom: 0; 27 padding: 1rem; 28 background: white; 29 border-top: 1px solid #eee; 30}

Debugging Sticky#

1/* Debug: visualize sticky element */ 2.sticky-debug { 3 position: sticky; 4 top: 0; 5 outline: 2px solid red; 6} 7 8/* Debug: check parent overflow */ 9.parent * { 10 outline: 1px solid blue; 11} 12 13/* Check in DevTools: 14 1. Inspect sticky element 15 2. Check computed 'position' 16 3. Check all parent 'overflow' values 17 4. Verify parent has height 18 5. Check z-index stacking */

Best Practices#

Setup: ✓ Always set a threshold (top/bottom) ✓ Ensure parent has height ✓ Check parent overflow values ✓ Use align-self in flex/grid Styling: ✓ Add background to prevent transparency ✓ Use z-index for stacking ✓ Add shadow when stuck ✓ Consider backdrop-filter Performance: ✓ Avoid complex shadows while scrolling ✓ Use will-change sparingly ✓ Keep sticky content simple ✓ Test on mobile devices Responsive: ✓ Adjust top value for header heights ✓ Consider disabling on mobile ✓ Test in all viewport sizes ✓ Account for dynamic content

Conclusion#

Position sticky is powerful but requires proper setup. Ensure parents have height and visible overflow, always set a threshold value, and use align-self in flex/grid layouts. Debug with outlines and DevTools when sticky doesn't work as expected.

Share this article

Help spread the word about Bootspring