A sticky header keeps navigation reachable as visitors scroll, instead of it disappearing above the fold the moment they move down the page. There are two real ways to get one in WordPress: plain CSS you control yourself, or a block-native setting if your theme (or a blocks plugin) already supports it.

Method 1: Pure CSS
The underlying mechanism is a single CSS property: position: sticky (with top: 0) applied to your header element, plus a z-index high enough that content scrolling underneath doesn’t render on top of it. Add this through your theme’s custom CSS panel (Appearance → Customize → Additional CSS in most themes) targeting your specific header’s class or ID:
.site-header {
position: sticky;
top: 0;
z-index: 999;
background-color: #ffffff; /* solid background, or scroll content shows through */
transition: box-shadow 0.2s ease;
}
.site-header.is-scrolled {
box-shadow: 0 2px 8px rgba(0,0,0,0.08); /* subtle shadow once scrolling starts */
}
The .is-scrolled class needs a small script toggling it based on scroll position if you want the shadow-on-scroll effect — roughly a dozen lines checking window.scrollY on a scroll listener. This works with zero added plugins, but it’s manual: you’re finding the right selector yourself, and any layout shift when the header pins can need extra CSS to smooth over.
A Better Way to Detect “Stuck”: IntersectionObserver Instead of a Scroll Listener
The window.scrollY scroll-listener approach above works, but it’s not the most efficient way to detect when a sticky header has actually pinned. A scroll listener that recalculates position on every scroll frame runs on the main thread, and can add jank on longer pages. The more performant pattern is a one-pixel-tall “sentinel” element placed just above the header, watched with an IntersectionObserver: when the sentinel scrolls out of view, the header has stuck, and the callback toggles the class instead of a per-frame position calculation.
const sentinel = document.querySelector('#sticky-sentinel');
const header = document.querySelector('.site-header');
const observer = new IntersectionObserver(
([entry]) => header.classList.toggle('is-scrolled', !entry.isIntersecting),
{ threshold: 0 }
);
observer.observe(sentinel);
IntersectionObserver runs off the main scroll thread, so it doesn’t fire on every scroll pixel the way a naive scroll listener does — it only fires when the sentinel actually crosses the viewport boundary. For a single header shadow toggle the difference is negligible on a short page, but it’s the pattern worth reaching for if you’re also driving other scroll-linked effects on the same page.
Method 2: A Block-Native Setting
If your theme or blocks plugin builds sticky behavior into its own header/theme builder, it’s a toggle plus a few style options (background-on-scroll, shrink-on-scroll) rather than hand-written CSS. This is generally the faster, more maintainable route if the option already exists in your stack, since theme updates won’t risk breaking custom CSS you added separately.
Also Read: Nexter Theme Header Builder: Building a Sticky, Transparent, or Mega-Menu Header — the block-native version of this, including combining sticky with a transparent header.
Combining Sticky With a Transparent Header
A common design pattern is a transparent header over a hero image that becomes solid once the visitor scrolls past that hero. Doing this with hand-written CSS means toggling a background-color class at the same scroll threshold that would trigger the shadow above, rather than relying purely on :hover or static opacity — the header’s transparent state and its sticky state are two separate style rules that both need to be conditional on scroll position, not just one. This is the specific combination that’s noticeably easier through a block-native header builder, since the scroll-threshold logic is already wired up for you.
Sticky vs. Fixed: Why the Distinction Matters
position: sticky and position: fixed are often confused, but they behave differently in ways that affect layout decisions. A fixed header is detached from the document flow immediately and always pinned, which means the rest of your layout needs a matching top margin or padding to avoid content starting underneath it from the very first pixel of the page. A sticky header, by contrast, behaves like a normal, in-flow element until the page scrolls past its defined offset (top: 0, in the CSS above), at which point it starts sticking. For a header meant to sit normally at the top of the page and only pin once scrolling begins, sticky is almost always the right choice; fixed is better suited to an element that should be pinned unconditionally, like a floating chat widget.
A 2026 CSS-Only Alternative: container-type: scroll-state
Recent CSS adds a scroll-state container query, @container scroll-state(stuck: top), that lets you detect and style a stuck sticky element with zero JavaScript at all — no sentinel element, no IntersectionObserver, no scroll listener. You mark the header’s containing element with container-type: scroll-state, then write the “stuck” styling as a container query rule. Browser support is still rolling out unevenly across engines as of 2026, so the IntersectionObserver sentinel pattern above remains the safer, broadly-compatible fallback for a production site today, but it’s worth knowing this is where sticky-header styling is heading.
Accessibility: What a Sticky Header Shouldn’t Break
A sticky header permanently occupies vertical space, which has two accessibility consequences worth checking deliberately. First, a keyboard user tabbing through the page can end up with focus landing on an element that’s visually hidden behind the sticky header, since browsers scroll a focused element into view but don’t know to account for a fixed-position obstruction on top of it — the same scroll-padding-top fix mentioned above for anchor links also helps here. Second, if your site has a “skip to content” link for screen-reader and keyboard users, verify it still lands visibly below the sticky header rather than behind it, since skip links are one of the more commonly-forgotten elements when a header changes from static to sticky.
Things That Commonly Go Wrong
- Content jump on load — if the header’s height isn’t accounted for elsewhere in your layout, the page content can visibly shift when the sticky positioning kicks in. Reserve the header’s height in your layout ahead of time to avoid this, e.g. with a
scroll-padding-topequal to the header’s height on thehtmlelement, so anchor-link jumps don’t land content underneath the sticky header either. - Dropdown menus getting clipped — a parent element with
overflow: hiddenanywhere between the sticky header and the page root will cut off dropdown submenus. This is the single most common sticky-header bug report. - Mobile behavior — decide deliberately whether the header should stay sticky on small screens; a sticky header eating a large share of a small viewport can hurt more than it helps.
- A sticky ancestor with a fixed height —
position: stickyonly works within the bounds of its parent container; if a parent has a fixed, shorter height than the page (or its ownoverflowset to something other thanvisible), the header will stop sticking once it reaches the parent’s edge, not the viewport’s.
Also Read: How to Add a Mega Menu in WordPress (4 Ways, With and Without a Plugin) — a sticky header and a mega menu are commonly built together, and both live in the same header template.
FAQ
Does a sticky header hurt page performance?
position: sticky is a native CSS feature with negligible performance cost; it doesn’t require JavaScript scroll listeners the way older “fixed on scroll” implementations sometimes did. A scroll listener is only needed for the optional shadow/background-swap effect, not the sticky behavior itself, and an IntersectionObserver-based sentinel is a lighter-weight way to implement even that.
Why does my dropdown menu get cut off when the header is sticky?
Check for an overflow: hidden rule on any parent container between the header and the page root; it’s the most common cause of this specific bug.
Should my header be sticky on mobile too?
It’s a deliberate design choice, not a default best practice either way — weigh how much of a small viewport a sticky header takes up against the navigation convenience it offers.
Why do anchor links land content underneath my sticky header?
Set scroll-padding-top on the html element equal to your header’s height — without it, a same-page anchor jump scrolls the target section right up against the viewport edge, which then sits behind the fixed header.
Why did my sticky header stop sticking partway down the page?
position: sticky is bounded by its nearest parent container, not the whole page. If that parent has a fixed height shorter than the full page, or an overflow value other than visible, the header stops sticking once it hits that parent’s boundary rather than the browser viewport.
Conclusion
Whichever method you pick, the failure modes to watch for are the same: a layout jump on load, a clipped dropdown from an overflow rule somewhere in the header’s parent chain, a sticky-breaking parent container, and anchor links landing behind the header without a matching scroll-padding value. Test all four before calling a sticky header finished, not just the scroll behavior itself.
Suggested Reading
- Nexter Theme Header Builder: Building a Sticky, Transparent, or Mega-Menu Header
- How to Add a Mega Menu in WordPress (4 Ways, With and Without a Plugin)
- WordPress Dark Mode: How to Add a Site-Wide Light/Dark Toggle










