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 verifiedUses 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.jsonMethod 02 · Copy and paste
- 1. Install the dependencies below.
- 2. Copy the source from the Code tab into
components/ui/auto-hide-header.tsx. - 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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Header content (brand, navigation, actions). |
| container | RefObject<HTMLElement | null> | — | Scrollable element to track. Defaults to the window. |
| tolerance | number | 8 | Pixels scrolled in one direction before hiding or reappearing. |
| offset | number | 64 | Always shown within this many px of the top. |
| spring | { stiffness, damping, mass } | { 500, 42, 0.8 } | Spring for the slide. |
| elevate | boolean | true | Transparent at the top; gains surface, border and shadow once scrolled. When false the header is always solid. |
| pinned | boolean | false | Keep the header visible regardless of scroll. |
| onVisibilityChange | (visible: boolean) => void | — | Called when the header hides or reappears (not on every scroll). |
| ...props | HTMLAttributes | — | 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
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).