Skip to main content
Custom React hook that detects the scroll direction (up or down) with a configurable threshold to prevent jitter.

Import

import useScrollDirection from "../hooks/useScrollDirection"

Signature

function useScrollDirection(
  initialDirection?: 'up' | 'down', 
  threshold: number
): string

Parameters

initialDirection
'up' | 'down'
default:"'up'"
The initial scroll direction before any scrolling occurs.
threshold
number
required
Minimum scroll distance (in pixels) required to trigger a direction change. Prevents jitter from small scroll movements.

Returns

direction
string
The current scroll direction: 'up' or 'down'.

Usage

const Header = () => {
  const direction = useScrollDirection('up', 10)

  return (
    <header 
      className={`
        fixed top-0 transition-transform
        ${direction === 'down' ? '-translate-y-full' : 'translate-y-0'}
      `}
    >
      <nav>Navigation content</nav>
    </header>
  )
}

Implementation Details

  • Performance: Uses requestAnimationFrame to throttle scroll event updates
  • Threshold: Only updates direction when scroll distance exceeds threshold
  • State Management: Tracks last scroll position to calculate direction
  • Cleanup: Removes scroll event listener on component unmount
  • Edge Case: Handles negative scroll positions (sets to 0)

Examples

Hide Header on Scroll Down

const AutoHideHeader = () => {
  const scrollDirection = useScrollDirection('up', 5)
  
  return (
    <header 
      style={{
        transform: scrollDirection === 'down' 
          ? 'translateY(-100%)' 
          : 'translateY(0)',
        transition: 'transform 0.3s ease'
      }}
    >
      <h1>My App</h1>
    </header>
  )
}

Show Scroll-to-Top Button

const ScrollToTop = () => {
  const direction = useScrollDirection('up', 20)
  const [showButton, setShowButton] = useState(false)

  useEffect(() => {
    // Show button when scrolling up and past a certain point
    const handleScroll = () => {
      setShowButton(window.scrollY > 300 && direction === 'up')
    }
    window.addEventListener('scroll', handleScroll)
    return () => window.removeEventListener('scroll', handleScroll)
  }, [direction])

  return showButton ? (
    <button onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
      ↑ Back to top
    </button>
  ) : null
}

Dynamic Navbar Style

const Navbar = () => {
  const direction = useScrollDirection('up', 15)
  const isScrollingDown = direction === 'down'

  return (
    <nav className={isScrollingDown ? 'navbar-compact' : 'navbar-full'}>
      {/* Navbar content */}
    </nav>
  )
}

Threshold Guidelines

  • Small threshold (5-10px): More responsive, may trigger on small movements
  • Medium threshold (15-25px): Balanced responsiveness and stability
  • Large threshold (30-50px): Only triggers on intentional scroll gestures
Use a larger threshold (20-30px) for mobile devices to avoid triggering on scroll bounce effects.

Common Use Cases

  • Auto-hiding headers/navigation bars
  • Showing/hiding floating action buttons
  • Triggering animations based on scroll direction
  • Optimizing scroll-based UI interactions
  • Conditional content loading
The hook uses requestAnimationFrame for performance optimization, ensuring direction updates don’t block the main thread.

Build docs developers (and LLMs) love