CSS position: sticky is a hybrid positioning value that behaves like position: relative until the element reaches a defined threshold (set with top, bottom, left, or right), at which point it “sticks” and behaves like position: fixed relative to its scroll container. It was designed specifically to solve the sticky header and sticky sidebar use cases that previously required JavaScript scroll listeners or the position: fixed hack with JavaScript-calculated offsets. The key things to understand: the element stays within the bounds of its parent container — it will not stick beyond the parent’s bottom edge. This is why sticky navbars sometimes stop too early: the parent container ends before the bottom of the page. You must set at least one of top/bottom/left/right for sticky to work — just setting position: sticky alone does nothing. The parent must not have overflow: hidden, overflow: auto, or overflow: scroll — any of these break stickiness. On mobile, some older Android browsers partially supported sticky but modern support is universal. Sticky elements are GPU-composited in modern browsers, making them far more performant than JavaScript-based sticky implementations. Combined with CSS custom properties (covered in the CSS variables guide) and CSS Grid (from the grid guide), sticky positioning is a fundamental layout tool for modern WordPress themes.
Problem: You want a sticky admin bar, sidebar, or table header that follows the user as they scroll, without JavaScript.
Solution: Use the following CSS patterns in your theme stylesheet:
/* ── Sticky navigation bar ─────────────────────────────────────────────────── */
.site-header {
position: sticky;
top: 0; /* stick to the top of the viewport */
z-index: 100; /* sit above page content */
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,.1);
}
/* ── Sticky sidebar (in a two-column layout) ───────────────────────────────── */
.sidebar {
position: sticky;
top: 20px; /* 20px gap from top of viewport */
align-self: start; /* required inside a CSS Grid or Flexbox parent */
max-height: calc(100vh - 40px);
overflow-y: auto;
}
/* Grid wrapper for the layout */
.content-wrapper {
display: grid;
grid-template-columns: 1fr 300px;
gap: 40px;
align-items: start; /* critical — without this sticky sidebar breaks */
}
/* ── Sticky table header ───────────────────────────────────────────────────── */
.data-table thead th {
position: sticky;
top: 0;
background: #f5f5f5;
z-index: 1;
}
/* ── Fix: sticky stops too early (parent has overflow hidden) ──────────────── */
/* Wrong — this kills stickiness: */
/* .parent { overflow: hidden; } */
/* Correct — use clip instead: */
.parent {
overflow: clip; /* clips visually but does not create a scroll container */
}
/* ── Sticky element with WordPress admin bar (32px tall) ───────────────────── */
.is-admin-bar-showing .site-header {
top: 32px;
}
@media screen and (max-width: 782px) {
.is-admin-bar-showing .site-header {
top: 46px;
}
}
NOTE: The single most common reason position: sticky does not work is an ancestor element with overflow: hidden or overflow: auto. Use browser DevTools to inspect the parent chain and look for any element with a non-visible overflow value. The align-self: start rule on the sticky sidebar is equally critical — without it, in a flex or grid parent, the sidebar stretches to the full height of the row, making sticky have nothing to scroll against. Also note that position: sticky does not work on <tr>, <td>, or <th> elements in Firefox unless you use border-collapse: separate on the table.