< All Blog

How to Toggle Header on Scroll with React.js

October 18, 2022

This blog post explains how I implemented togglable header in React.js.


The following shows how the header gets toggled as users scroll the pages:

the header behaviour


Let's have a simple Header component. This component assumes that useScrollDetection() function is one of the Effect Hooks and will return Boolean. We toggle the state using CSS, and here different classnames are appended based on the state.

const Header = () => {
  const isHidden = useScrollDirection()
  return (
            `header ${isHidden ? "header-hide" : "header-show"}`

Here is CSS snippets. Use CSS transitions to achieve smooth hiding/showing effects.

.header {
  position: fixed;
  transition-property: all;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 500ms;

.header-show {
  top: 0px;

.header-hide {
  top: -6rem;

Finally, this is the core logic of the Effect Hook. The key here is to change the state by checking how fast users scroll pages, using diff = 4. You can change this value to tune to your preferences. For example, if you have larger values for this diff, users need to scroll much faster to toggle your header component.

Don't forget to remove event listeners in the cleanup phase, which can be achieve by writing the return function for the effect hook.

function useScrollDirection() {
  const [isHidden, setIsHidden] = useState(false);

  useEffect(() => {
    // store the last scrolled Y to detect how fast users scroll pages
    let lastScrollY = window.pageYOffset

    const updateScrollDirection = () => {
      const scrollY = window.pageYOffset
      const goingDown = scrollY > lastScrollY
      const diff = 4
      // There are two cases that the header might want to change the state:
      // - when scrolling up but the header is hidden
      // - when scrolling down but the header is shown
      // stateNotMatched variable decides when to try changing the state
      const stateNotMatched = goingDown !== isHidden
      const scrollDownTooFast = scrollY - lastScrollY > diff
      const scrollUpTooFast = scrollY - lastScrollY <- diff

      const shouldToggleHeader = stateNotMatched && (scrollDownTooFast || scrollUpTooFast)
      if (shouldToggleHeader) {
      lastScrollY = scrollY > 0 ? scrollY : 0

    window.addEventListener("scroll", updateScrollDirection)
    return () => {
      window.removeEventListener("scroll", updateScrollDirection)
  }, [isHidden]);

  return isHidden;


That's it. The core logic is fairly simple, isn't it? Also you can encapsulate the toggle logic into one single function. This is one of the thing I love for React.

Recommended Posts

  1. How Books page is Built

    November 06, 2022
    As I wrote in this blog, I created the Books page to list the books I have ever read. In this blog post, I'll explain how this page is built. Data Model At fir…