Difficulty: Intermediate
position: sticky is a hybrid of relative and fixed positioning. A sticky element behaves like position: relative within its parent's flow until it reaches a specified scroll threshold, at which point it behaves like position: fixed and stays in place. Once the user scrolls past the parent's boundary, the sticky element scrolls away with its parent. This creates the widely used "sticky header" pattern without any JavaScript.
The scroll threshold is defined by the offset properties. Setting position: sticky; top: 0 means the element will stick to the top of the viewport (or the nearest scrollable ancestor) when the user scrolls past it. The element remains in the normal document flow before and after the sticky region. Unlike position: fixed, it does not create gaps in the layout because it still occupies space in the document.
A critical constraint that catches many developers: a sticky element can only stick within its parent's boundaries. Once the parent element scrolls out of view, the sticky child scrolls away with it. This means the sticky behavior is bounded by the parent - the element will never stick beyond its parent's bottom edge. If your sticky element is not working, check whether the parent has enough height for the element to actually stick.
The overflow property on ancestor elements can break sticky positioning. If any ancestor between the sticky element and the scrolling container has overflow: hidden, overflow: auto, or overflow: scroll, the sticky behavior may fail. This is because sticky positioning requires the element to be within a scrollable context, and an overflow container creates a new scroll context that may not match the one you expect.
Common use cases for position: sticky include table headers that stay visible while scrolling through long tables, section headers in alphabetical lists (like a phone contacts app), sidebar navigation that sticks until the footer approaches, and scroll-spy-style navigation. It requires no JavaScript, performs well because the browser handles it natively, and degrades gracefully in older browsers (the element simply scrolls normally).
<style>
.sticky-header {
position: sticky;
top: 0;
background: #1e293b;
color: white;
padding: 16px 24px;
z-index: 10;
}
.content-section {
padding: 20px;
min-height: 400px;
}
</style>
<div class="sticky-header">
<strong>Sticky Navigation</strong>
</div>
<div class="content-section">
<h2>Section 1</h2>
<p>Scroll down to see the header stick to the top...</p>
<p>The header stays in view as you scroll through content.</p>
</div>
<div class="content-section" style="background: #f8fafc;">
<h2>Section 2</h2>
<p>The header is still sticky here because its parent is the body.</p>
</div>
The header sticks to the top of the viewport when scrolled past (top: 0). Unlike position: fixed, it does not need extra margin on the content below because it remains in the document flow until the threshold is reached.
<style>
.contact-list {
max-height: 400px;
overflow-y: auto;
border: 1px solid #e2e8f0;
width: 300px;
}
.letter-header {
position: sticky;
top: 0;
background: #3b82f6;
color: white;
padding: 8px 16px;
font-weight: bold;
z-index: 1;
}
.contact {
padding: 12px 16px;
border-bottom: 1px solid #f1f5f9;
}
</style>
<div class="contact-list">
<div class="letter-header">A</div>
<div class="contact">Alice Anderson</div>
<div class="contact">Alex Baker</div>
<div class="contact">Anna Chen</div>
<div class="letter-header">B</div>
<div class="contact">Bob Davis</div>
<div class="contact">Beth Evans</div>
<div class="contact">Brian Foster</div>
<div class="letter-header">C</div>
<div class="contact">Carol Green</div>
<div class="contact">Chris Hall</div>
<div class="contact">Claire Irwin</div>
</div>
Each letter header sticks to the top of the scrollable container. When the next letter header reaches the top, it pushes the previous one away. This pattern is commonly seen in phone contacts and alphabetical lists.
<style>
.layout {
display: flex;
gap: 24px;
padding: 24px;
}
.main-content {
flex: 1;
min-height: 1500px;
background: #f8fafc;
padding: 24px;
}
.sidebar {
width: 250px;
}
.sidebar-inner {
position: sticky;
top: 20px;
background: white;
border: 1px solid #e2e8f0;
padding: 20px;
border-radius: 8px;
}
</style>
<div class="layout">
<div class="main-content">
<h1>Main Content</h1>
<p>Scroll down. The sidebar sticks until the layout ends.</p>
</div>
<div class="sidebar">
<div class="sidebar-inner">
<h3>Sidebar</h3>
<nav>
<a href="#section1">Section 1</a><br>
<a href="#section2">Section 2</a><br>
<a href="#section3">Section 3</a>
</nav>
</div>
</div>
</div>
The sidebar-inner element sticks 20px from the top while scrolling. It stops sticking when the layout container ends, ensuring it never overlaps the footer. The sidebar div wraps the sticky element so it has room to stick within.
<style>
.table-wrapper {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e2e8f0;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
position: sticky;
top: 0;
background: #1e293b;
color: white;
padding: 12px 16px;
text-align: left;
z-index: 1;
}
tbody td {
padding: 10px 16px;
border-bottom: 1px solid #f1f5f9;
}
tbody tr:hover {
background: #f8fafc;
}
</style>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr><td>Alice</td><td>alice@example.com</td><td>Engineer</td></tr>
<tr><td>Bob</td><td>bob@example.com</td><td>Designer</td></tr>
<tr><td>Carol</td><td>carol@example.com</td><td>Manager</td></tr>
<tr><td>David</td><td>david@example.com</td><td>Engineer</td></tr>
<tr><td>Eve</td><td>eve@example.com</td><td>Designer</td></tr>
<tr><td>Frank</td><td>frank@example.com</td><td>Analyst</td></tr>
<tr><td>Grace</td><td>grace@example.com</td><td>Engineer</td></tr>
<tr><td>Hank</td><td>hank@example.com</td><td>Manager</td></tr>
</tbody>
</table>
</div>
The th elements in thead have position: sticky; top: 0, keeping the column headers visible while scrolling through table rows. The table wrapper has overflow-y: auto to create the scrollable context.
position: sticky, Scroll Threshold, Sticky Headers, Sticky Sidebar, Overflow Constraint