React Headroom: The Complete Guide to Auto-Hiding Navigation Headers






React Headroom: Auto-Hiding Header Setup & Customization Guide




React Headroom: The Complete Guide to Auto-Hiding Navigation Headers


Your navigation bar is prime real estate β€” but it shouldn’t hog the screen when the user is trying to read.
React Headroom
solves this with elegance: the header disappears on scroll down, reappears on scroll up, and stays completely
out of your way until it’s needed. This guide covers everything from installation to advanced customization,
with real code and zero padding (the textual kind, not the CSS kind).

What Is React Headroom and Why Should You Care?

React Headroom
is a lightweight React component that wraps your navigation header and controls its visibility based on scroll
direction and position. It’s a React port of the original
Headroom.js library by Nick Williams,
rebuilt as a proper React component with props-driven configuration, lifecycle-aware scroll listeners, and zero
external runtime dependencies beyond React itself.

The core concept is deceptively simple: when the user scrolls down past a defined offset, the header slides out
of view. When they scroll back up β€” even mid-page β€” the header slides back in. This “smart header” pattern
dramatically improves content readability on mobile, where vertical space is scarce, while keeping navigation
accessible without forcing users back to the top of the page.

In practical terms, this translates to a better user experience score, reduced layout shift complaints, and
noticeably higher engagement on long-form content pages. If your header currently eats 60–80px of viewport on
every scroll, you’ll feel the difference immediately once you implement a
React auto-hiding header.



react-headroom β€” CSS state classes

πŸš€ MyApp

Headroom adds these CSS classes dynamically based on scroll state:

headroom–pinned
headroom–unpinned
headroom–top
headroom–not-top

Installing React Headroom: Getting the Basics Right

React Headroom installation
is about as painless as it gets. The package is published on npm under the name react-headroom and
has no peer dependencies other than React and ReactDOM (versions 16.3+ are fully supported, and it works
perfectly with React 18). One command, and you’re in.



bash

# npm
npm install react-headroom

# yarn
yarn add react-headroom

# pnpm
pnpm add react-headroom

Once installed, the import syntax is standard and clean. You bring in the default export from the package,
wrap your existing header JSX with the <Headroom> component, and you’re done with the basic
react-headroom setup.
No provider, no context, no configuration file required at this stage. The component self-initializes its
scroll listener when it mounts and cleans it up when it unmounts β€” properly, without memory leaks.



jsx β€” App.jsx (minimal setup)

import React from 'react';
import Headroom from 'react-headroom';

function App() {
  return (
    <div>
      <Headroom>
        <nav className="site-nav">
          <a href="/">Logo</a>
          <ul>
            <li><a href="/about">About</a></li>
            <li><a href="/blog">Blog</a></li>
          </ul>
        </nav>
      </Headroom>

      <main>
        {/* page content */}
      </main>
    </div>
  );
}

export default App;

πŸ“Œ Note on positioning: By default, Headroom renders a wrapper <div> with
position: sticky or fixed behavior depending on the scroll state. Make sure your
layout accounts for the header height β€” otherwise you’ll get content jumping under the nav when the page loads.
The calcHeightOnResize prop helps with this on dynamic layouts.

Understanding the Props: Your Configuration API

Where react-headroom really earns its keep is in its props-driven configuration system. Every scroll-detection
behavior is tunable through a clean set of props, which means you’re not fighting the library to make your
header behave exactly the way your design requires. Let’s walk through the ones you’ll actually use.

The pinStart prop defines the scroll position (in pixels) at which the header begins to react.
Below this value, the header is always visible regardless of scroll direction β€” useful when you want the hero
section to coexist peacefully with your nav. The downTolerance and upTolerance props
set scroll thresholds that prevent the header from flickering on tiny, accidental micro-scrolls. Setting these
to something like 5 and 5 respectively usually solves the “header dancing” problem
that plagues naive scroll-detection implementations.

The disable prop is a boolean escape hatch β€” when set to true, Headroom becomes a
transparent wrapper and your header stays visible at all times. This is perfect for conditionally disabling the
scroll behavior on certain routes (modal-heavy pages, for instance) without unmounting and remounting the
component. It’s the kind of thoughtful API decision that tells you the library was written by someone who
actually had to maintain a large React codebase.

Prop Type Default Description
pinStart Number 0 Scroll offset (px) before headroom activates
upTolerance Number 5 Px scrolled up before header reappears
downTolerance Number 0 Px scrolled down before header hides
disable Boolean false Disables all scroll behavior when true
onPin Function β€” Callback fired when the header is pinned (shown)
onUnpin Function β€” Callback fired when the header is unpinned (hidden)
onUnfix Function β€” Callback fired when the header returns to top position
style Object β€” Inline styles applied to the wrapper div
className String β€” Custom class added to the wrapper div
wrapperStyle Object β€” Styles for the outer placeholder wrapper
parent Function () => window Returns the scroll parent element
calcHeightOnResize Boolean true Recalculates header height on window resize

A Real-World React Headroom Example

Theory is fine, but let’s look at a production-representative
react-headroom example
that goes beyond the bare minimum. The following component includes scroll-aware styling
(the nav changes appearance once the user leaves the top of the page), callback logging for analytics events,
and a custom scroll parent for cases where your page scrolls inside a container element rather than the window.



jsx β€” Navigation.jsx (real-world example)

import React, { useState } from 'react';
import Headroom from 'react-headroom';
import './Navigation.css';

function Navigation() {
  const [isPinned, setIsPinned] = useState(true);

  const handlePin = () => {
    setIsPinned(true);
    // optional: fire analytics event
    console.log('Header pinned');
  };

  const handleUnpin = () => {
    setIsPinned(false);
    console.log('Header unpinned');
  };

  return (
    <Headroom
      pinStart={80}
      upTolerance={5}
      downTolerance={10}
      onPin={handlePin}
      onUnpin={handleUnpin}
      style={{
        zIndex: 1000
      }}
    >
      <nav
        className={`site-nav ${isPinned ? 'nav--visible' : 'nav--scrolled'}`}
      >
        <div className="nav-logo">MyApp</div>
        <ul className="nav-links">
          <li><a href="/features">Features</a></li>
          <li><a href="/pricing">Pricing</a></li>
          <li><a href="/docs">Docs</a></li>
        </ul>
      </nav>
    </Headroom>
  );
}

export default Navigation;

Notice the pinStart={80} β€” this tells headroom to stay fully visible for the first 80 pixels of
scroll, which typically covers the height of a hero section or introductory banner. The user gets to see the
full header context before the auto-hide behavior kicks in. Combined with downTolerance={10}, the
header won’t vanish unless the user has genuinely committed to scrolling down, not just nudged the trackpad.

The isPinned state being tracked separately might look redundant at first β€” after all, headroom
manages its own internal state. But exposing it to your component tree gives you the ability to change the
visual appearance of the nav (background opacity, shadow, blur effects) independently of the show/hide
animation. This pattern is commonly used to create that “transparent header at top, solid header when
scrolled” effect you see on virtually every modern SaaS landing page.

CSS Animations with React Headroom: Making It Look Intentional

The default react-headroom animations
are functional but minimal β€” the header moves without a transition by default, which can feel abrupt depending
on your design. The good news is that Headroom applies a predictable set of CSS classes to its wrapper, and
you own all the animation logic. There’s no inline transition to fight or override; you simply write the CSS
and the component uses it.



css β€” Navigation.css (smooth transitions)

/* Base headroom wrapper */
.headroom {
  will-change: transform;
  transition: transform 300ms ease-in-out, box-shadow 300ms ease;
}

/* Header is visible and fixed at the top */
.headroom--pinned {
  transform: translateY(0%);
}

/* Header is hidden above the viewport */
.headroom--unpinned {
  transform: translateY(-100%);
}

/* User is at the very top of the page */
.headroom--top {
  background: transparent;
  box-shadow: none;
}

/* User has scrolled away from the top */
.headroom--not-top {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(12px);
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08);
}

/* Your actual nav element */
.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  height: 64px;
}

The will-change: transform declaration is worth calling out. It hints to the browser’s compositor
that this element will be animated, allowing it to promote the header to its own GPU layer ahead of time. The
result is a silky-smooth slide transition with no paint jank β€” especially noticeable on mid-range Android
devices where CSS animations tend to drop frames without this optimization. Add it, keep it, thank yourself
later.

The backdrop-filter: blur(12px) on .headroom--not-top is entirely optional but
produces the frosted-glass navigation aesthetic that’s been popular since macOS Big Sur made it mainstream.
Pair it with a semi-transparent rgba background and your nav will feel premium with about eight
lines of CSS. Just be aware that backdrop-filter has performance implications on lower-end
hardware, and it requires a fallback for older browsers that don’t support it.

Advanced Customization: Scroll Inside a Container

The default parent prop resolves to window, which works for the vast majority of
layouts. But if your application uses a scrollable container element β€” a common pattern in dashboard UIs,
modals, or split-pane layouts β€” you’ll need to tell Headroom which element to watch. The parent
prop accepts a function that returns the DOM node you want to observe.



jsx β€” Scroll container setup

import React, { useRef } from 'react';
import Headroom from 'react-headroom';

function DashboardLayout() {
  const scrollRef = useRef(null);

  return (
    <div
      ref={scrollRef}
      style={{
        height: '100vh',
        overflowY: 'auto'
      }}
    >
      {/* parent returns the scrollable container, not window */}
      <Headroom parent={() => scrollRef.current}>
        <nav className="dashboard-nav">
          Dashboard Navigation
        </nav>
      </Headroom>

      <main className="dashboard-content">
        {/* long scrollable content */}
      </main>
    </div>
  );
}

export default DashboardLayout;

The function form of the parent prop (rather than a direct ref) is intentional: Headroom calls it
during mount, at which point the ref’s .current is guaranteed to be populated. If you passed the
ref directly as a value, you’d risk passing null and spending an enjoyable afternoon debugging
why your scroll detection silently stopped working.

⚠️ Common gotcha: When using a custom scroll parent, ensure the container has
overflow-y: auto or overflow-y: scroll set explicitly β€” not just
overflow: hidden on a parent. Headroom listens for the scroll event on the target
element; if the element doesn’t actually scroll, the listener never fires and your header stays frozen.

Using React Headroom with React Router, Next.js and Gatsby

Modern React applications almost never exist in isolation β€” they live inside routing frameworks, and navigation
headers need to survive route changes without breaking their scroll state. Fortunately,
React Headroom
plays nicely with React Router and framework-level solutions like Next.js and Gatsby, but there’s one
footgun worth knowing about.

When a route changes, the scroll position of the window may not reset to zero automatically β€” especially in
Next.js App Router with soft navigation. This can leave Headroom in an unpinned state (header hidden)
immediately after landing on a new page, which is confusing to users who haven’t scrolled anything yet. The
fix is to use the disable prop conditionally during navigation transitions, or to imperatively
reset scroll position in your router’s transition hooks and let the
onUnfix callback signal when Headroom has re-anchored to the top.

For Next.js specifically, you’ll typically place the <Headroom> component inside your
root layout file (app/layout.tsx in App Router, or _app.tsx in Pages Router) so it
persists across route changes. The component handles its own scroll listener cleanup correctly on unmount, but
since it won’t unmount between soft navigations, you may want to reset its internal state via the
disable={isNavigating} pattern. It’s three lines of code and saves your users from a genuinely
disorienting UX moment.

βœ… Pro tip for Gatsby: Gatsby’s gatsby-browser.js exposes an
onRouteUpdate hook where you can window.scrollTo(0, 0) after each navigation.
Combined with Headroom’s pinStart={0}, this ensures the header is always visible at the top
of every new page β€” which is the correct behavior 99% of the time.

Accessibility Considerations for Auto-Hiding Headers

Auto-hiding navigation headers are a UX win for sighted mouse/trackpad users, but they require some additional
thought for keyboard and assistive technology users. A header that disappears mid-content can strand keyboard
focus in an invisible element if the user was navigating through the nav links when the header unpinned. This
isn’t a react-headroom bug β€” it’s a general challenge with any hide-on-scroll pattern.

The most robust solution is to use the onUnpin callback to manage focus: if the currently focused
element is inside the Headroom wrapper, move focus to the main content area before the header hides. You can
detect this with document.activeElement and a ref to the nav container. This pattern is
straightforward to implement and fully covers the keyboard navigation edge case without disabling the
auto-hiding behavior for everyone.

Also worth adding: an aria-label="Main navigation" on the <nav> element inside
Headroom, and a skip-to-main-content link positioned before the Headroom component. Screen reader users
typically expect a skip link as the first focusable element on the page, and since Headroom renders a fixed
wrapper, the skip link needs to sit outside and above the Headroom div in DOM order to be announced first.
These are not react-headroom-specific concerns, but they become visible when you add scroll-detection behavior
to a previously static header.

Performance: What React Headroom Actually Does Under the Hood

Understanding the internals helps you make informed decisions about where Headroom fits in your performance
budget. The library attaches a single scroll event listener to the parent element (default:
window). This listener is throttled using
requestAnimationFrame,
meaning it runs at most once per frame (60fps cap) regardless of how fast the user scrolls. This is the
correct approach β€” raw scroll events fire at much higher frequencies than the browser can paint, and
processing all of them is wasted computation.

State changes (pin/unpin) are handled via setState on the component’s internal class state,
which triggers a re-render of the Headroom wrapper element only β€” not the entire tree. Your nav children
won’t re-render unless they receive new props from the parent. This means the performance overhead is minimal
and confined: a single rAF-throttled scroll handler plus a CSS class toggle on one DOM node.
On the performance profiler, it barely registers.

Where you can accidentally hurt performance is in the callbacks. If you pass an onPin or
onUnpin handler that triggers a state update in a parent component β€” and that parent component
has many children β€” you could cause a broader re-render tree on every pin/unpin event. Keep the state that
lives in those callbacks as localized as possible, or memoize downstream components with
React.memo to prevent unnecessary re-renders. The library itself is not the bottleneck; your
response to its callbacks might be.


Quick Reference: React Headroom Integration Checklist

  • Install via npm install react-headroom β€” no additional peer dependencies needed beyond React
  • Wrap your <nav> or header element with <Headroom>
  • Set pinStart to match your hero/banner height so the header stays visible above the fold
  • Add upTolerance and downTolerance (5–10px each) to prevent micro-scroll jitter
  • Use CSS classes .headroom--pinned, .headroom--unpinned, .headroom--top, .headroom--not-top for all visual transitions
  • Add will-change: transform to your CSS for GPU-composited animation
  • Test keyboard navigation β€” ensure focused elements in the nav don’t disappear on unpin
  • Handle scroll-to-top on route changes in Next.js / Gatsby to prevent stale unpinned state

Comparing React Headroom to Alternatives

Before committing to react-headroom, it’s fair to ask whether it’s the right tool. The main alternatives in
the React ecosystem are building your own hook with useEffect + window.addEventListener,
using a general-purpose scroll animation library like Framer Motion’s scroll hooks, or leveraging the
native CSS position: sticky
without any JavaScript at all.

CSS sticky is the right call when you simply want the header to stay visible as the user scrolls β€” no
hide/show, just “follow me.” It’s zero JavaScript, zero bundle size, and perfectly accessible. But it can’t
hide the header on scroll down and show it on scroll up β€” that bidirectional behavior requires JavaScript
scroll detection, and this is exactly the niche react-headroom fills. Building the same logic from scratch
with a custom hook is entirely possible, but you’d end up recreating most of what Headroom already provides:
the rAF throttle, the tolerance thresholds, the CSS class system, the scroll parent abstraction.
That’s 150 lines of tested code you don’t have to write.

Framer Motion’s useScroll and useMotionValue hooks give you more flexibility but
come with a much larger bundle footprint. If you’re already using Framer Motion throughout your project, it
makes sense to build the scroll-aware header with it rather than adding another dependency. If you’re not,
adding Framer Motion just for a React scroll header
is significant overkill. React Headroom’s gzipped footprint is under 3KB; Framer Motion’s is around 45KB.
The math is straightforward.

Frequently Asked Questions

How do I install react-headroom in my React project?

Run npm install react-headroom (or yarn add react-headroom) in your project root.
No additional dependencies are required beyond React 16.3+. Import the default export as
import Headroom from 'react-headroom' and wrap your navigation element with
<Headroom>...</Headroom>. That’s the entire setup for basic functionality.

Can I customize the scroll threshold in react-headroom?

Yes. Use the downTolerance prop to control how many pixels the user must scroll downward before
the header hides, and upTolerance for the reverse. Both default to small values (0 and 5
respectively). Setting them to 5–15 each prevents the header from toggling on
accidental micro-scrolls. The pinStart prop additionally lets you define a scroll offset below
which the header is always visible, regardless of scroll direction.

Does react-headroom support CSS animations and transitions?

React Headroom applies four CSS classes to its wrapper element β€” headroom--pinned,
headroom--unpinned, headroom--top, and headroom--not-top β€” based
on scroll state. You write your own CSS transitions targeting these classes. The recommended approach is
transition: transform 300ms ease-in-out with translateY(0%) for pinned and
translateY(-100%) for unpinned, plus will-change: transform for GPU compositing.
The library imposes no animation opinions of its own.

βš™ SEO Meta

Title:
React Headroom: Auto-Hiding Header Setup & Customization Guide
Description:
Learn how to install, configure, and customize react-headroom to build a smart auto-hiding navigation header in React. Step-by-step tutorial with examples.
Title length:
63 characters βœ“
Desc length:
155 characters βœ“

πŸ“Š Semantic Core

Primary β€” Core Keywords
react-headroom
react-headroom tutorial
react-headroom installation
react-headroom setup
react-headroom getting started
react-headroom example
Secondary β€” Intent Keywords
React auto-hiding header
React hide on scroll
React scroll header
React sticky navigation
React scroll detection
React navigation header
react-headroom animations
react-headroom customization
React header library
LSI β€” Synonyms & Related
sticky header React
auto-hide navbar React
smart header component
scroll-aware navigation
headroom.js React wrapper
scroll position listener React
fixed nav on scroll
header visibility toggle



Previous Is Brixton London’s next tech hipster hub?

About author

You might also like

Sorry, no posts were found.

0 Comments

No Comments Yet!

You can be first to comment this post!

Leave a Reply