Implementing Sticky Header/Footer on Website
Sticky elements are trivial via position: sticky, but proper implementation accounts for scroll direction, animation performance, safe-area on mobile, and anchor link conflicts.
Basic Sticky Header
Use position sticky with will-change optimization. Add shadow on scroll threshold. Hide on scroll down, show on scroll up for mobile.
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: #fff;
will-change: transform;
transition: box-shadow 0.2s ease;
}
.site-header--scrolled {
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
Hide on Scroll Down
Popular pattern: header disappears when scrolling down, appears on scroll up. Useful for space saving on mobile.
const THRESHOLD = 100
const HIDE_AFTER = 50
let scrollStart = 0
let hidden = false
function onScroll() {
const y = window.scrollY
const diff = y - lastScrollY
if (diff > 0) {
// Scroll down
if (!hidden && y > THRESHOLD && y - scrollStart > HIDE_AFTER) {
header.classList.add('site-header--hidden')
hidden = true
}
} else {
// Scroll up
if (hidden) {
header.classList.remove('site-header--hidden')
hidden = false
scrollStart = y
}
}
lastScrollY = y
}
React Hook for Sticky Logic
function useStickyHeader(hideOnScrollDown = true) {
const [state, setState] = useState({
isScrolled: false,
isVisible: true,
scrollY: 0,
})
useEffect(() => {
let ticking = false
const handler = () => {
if (ticking) return
ticking = true
requestAnimationFrame(() => {
const y = window.scrollY
setState(prev => ({
isScrolled: y > 10,
isVisible: hideOnScrollDown ? (y < 100 || prev.isVisible) : true,
scrollY: y,
}))
ticking = false
})
}
window.addEventListener('scroll', handler, { passive: true })
return () => window.removeEventListener('scroll', handler)
}, [hideOnScrollDown])
return state
}
Sticky Footer / Bottom Navigation
Use fixed positioning for persistent footer. Apply safe-area-inset for iPhone safe zone.
.bottom-nav {
position: fixed;
bottom: 0;
padding-bottom: env(safe-area-inset-bottom);
}
Anchor Link Conflict
Use scroll-margin-top to account for sticky header covering anchors:
[id] {
scroll-margin-top: calc(var(--header-height) + 16px);
}
Timeline
Sticky header with scroll shadow — 1–2 hours. With hide-on-scroll and React hook — half day. With bottom nav, safe-area, sticky sidebar — 1 day.







