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}Sticky Sidebar#
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}Sticky Footer#
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.