Skip to content
WANCRAFT

D.138Navigation & Menus

Auto-Hide Header

A sticky header that slides away as you scroll down and springs back the moment you scroll up.

NewReactMotionTailwindintermediate

Overview

Auto-Hide Header gives reading room back to the content: scroll down past an offset and the header slides out of view; scroll up by more than a small tolerance and it springs straight back. Scroll position comes from motion's useScroll and the slide is a spring on a motion value, so scrolling never re-renders React. It is always shown near the top, while pinned, and whenever focus is inside it, so keyboard users tabbing into the navigation never lose it. Once scrolled it gains a translucent surface, hairline border and shadow. It tracks the window by default or any scrollable container passed by ref, and reports visibility changes through a callback. With prefers-reduced-motion it still hides and shows, but instantly, without the slide.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/auto-hide-header.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/auto-hide-header.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/auto-hide-header.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { AutoHideHeader } from "@/components/ui/auto-hide-header";

export function AutoHideHeaderExample() {
  return (
    <AutoHideHeader>
      <nav aria-label="Main" className="mx-auto flex h-14 max-w-5xl items-center gap-6 px-6">
        <a href="/" className="font-semibold">Field Notes</a>
        <a href="/essays" className="ml-auto text-sm">Essays</a>
        <a href="/about" className="text-sm">About</a>
      </nav>
    </AutoHideHeader>
  );
}

03Props

PropTypeDefaultDescription
childrenReactNode—Header content (brand, navigation, actions).
containerRefObject<HTMLElement | null>—Scrollable element to track. Defaults to the window.
tolerancenumber8Pixels scrolled in one direction before hiding or reappearing.
offsetnumber64Always shown within this many px of the top.
spring{ stiffness, damping, mass }{ 500, 42, 0.8 }Spring for the slide.
elevatebooleantrueTransparent at the top; gains surface, border and shadow once scrolled. When false the header is always solid.
pinnedbooleanfalseKeep the header visible regardless of scroll.
onVisibilityChange(visible: boolean) => void—Called when the header hides or reappears (not on every scroll).
...propsHTMLAttributes—Forwarded to the <header>. data-state (visible | hidden) and data-elevated are set for styling.

04Features

  • 01Hides on scroll down, reveals on scroll up, with direction tolerance and a top offset
  • 02Spring slide on a motion value driven by useScroll: no React render per scroll event
  • 03Always visible near the top, while pinned, and when focus is inside it
  • 04Elevated surface (translucent, blurred, hairline border, shadow) once scrolled
  • 05Works with the window or any scroll container via a ref; ignores elastic overscroll
  • 06data-state and data-elevated attributes for custom styling, plus onVisibilityChange
motion · scroll-linkedmotion · springinput · scrollinput · focusinput · keyboard

05Accessibility

  • Renders a semantic <header> landmark; your navigation inside keeps its own semantics.
  • Focus anywhere inside the header (for example tabbing into its links) reveals it immediately and keeps it shown until focus leaves.
  • The hidden header is only translated off-screen, never display:none or inert, so it stays reachable by keyboard and assistive tech.
  • prefers-reduced-motion: the header still hides and shows (documented choice) but switches instantly with no slide; surface transitions are disabled too.

06Limitations

  • Uses position: sticky, so the header must be a direct part of the scrolling area's flow (not inside an overflow-hidden wrapper).