How to Reveal Sticky Header on Scroll Up

Published on
Updated on
6 min read
sticky header thumbnail

If you want to know how to create a sticky header in your react application, this post is for you. In this post, we are going to do that in a pro way as creating a custom hook .


On websites, users should see the full content when scrolling down, but they should see the navigation component, that is, the header, when scrolling up. It is very easy to give this experience we just need to know a boolean value whether the user's scroll direction is up or not.

Final Code

Code for the custom hook and the implementation with holy grail layout react application can be found from 👉 here

Custom Hook

import { useEffect, useState } from 'react'
import { off, on } from 'utils'
* useScrollingUp
* @returns {boolean}
const useScrollingUp = () => {
let prevScroll
//if it is SSR then check you are now on the client and window object is available
if (process.browser) {
prevScroll = window.pageYOffset
const [scrollingUp, setScrollingUp] = useState(false)
const handleScroll = () => {
const currScroll = window.pageYOffset
const isScrolled = prevScroll > currScroll
prevScroll = currScroll
useEffect(() => {
on(window, 'scroll', handleScroll, { passive: true })
return () => {
off(window, 'scroll', handleScroll, { passive: true })
}, [])
return scrollingUp
export default useScrollingUp

If you are going to use this hook for server side rendering, then window is not going to be available because our component still not mounted at the client so we can check whether the process is at the client then use window.pageYOffset; however if you are going to use for client side rendering with react framework then you don't have to worry about the if(process.browser) check.

As you see, there is a useEffect we add scroll event listener with a callback function named handleScroll, with some options, i.e. passive:true option. Event listener is attached when the component -which uses this hook- is initially mounted, and the event listener is removed while the component is unmounting. Below you can find a detailed explanation for the variables inside of the useEffect hook:

  • prevScroll: We define this variable outside of our callback function as prevScroll which holds vertical pixel value in other words scrolled amount of pixel that is pageYOffset or scrollY. It increases with the increasing amount of distance from top to down, in other words scrolled down.
  • currentScroll: We first define this variable inside of the callback that is the pageYOffset value while scrolling event occurs.
  • 🔊 Attention and repeating : prevScroll is the value when there is no scroll event whereas currentScroll is the current value while scrolling event.
  • Therefore when user changes direction to scrolling up, the currentScroll will be less than prevScroll, after comparison we set prevScroll to the currentScroll.


//exported from src/utils/index.js
export function on(obj, ...args) {
export function off(obj, ...args) {

What is we are going to do is, just use useScrolingUp hook and set conditional styles to our header make it sticky while scrolling up.

* Header
* @param {object} children is your menu link items
import useScrollingUp from '@/hooks/useScrollingUp'
const Header = ({ children }) => {
const isScrollingUp = useScrollingUp()
return <header className={`${isScrollingUp ? 'stickyHeader' : ''}`}>{children}</header>
export default Header
//App.css or App.scss file
header {
//...the rest is at the source code
box-shadow: none;
border: none;
z-index: 10;
transition: box-shadow 250ms ease-in-out;
.stickyHeader {
position: fixed;
top: 0;
left: 0;
width: 100%;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);


With this post, I want to show that you should not think that react hooks are just solutions for only your application's shared logic or state management, but they also can be used to style our components, or to determine UI component states, i.e. opening a modal, showing a sticky header etc.