Implements “headroom” behaviour — hides a fixed navbar on scroll down, shows it on scroll up (like headroom.js).
Usage
import { useHeadroom } from '@kivora/react';
function Navbar() {
const { pinned } = useHeadroom({ fixedAt: 80 });
return (
<header
className={`fixed top-0 transition-transform ${
pinned ? 'translate-y-0' : '-translate-y-full'
}`}
>
{/* Navbar content */}
</header>
);
}
Parameters
Scroll distance in pixels before hiding begins.
Minimum scroll delta in pixels to trigger hide/show.
Returns
true while the navbar should be pinned (visible).
Current scroll position in pixels.
Examples
Basic headroom navbar
function Header() {
const { pinned } = useHeadroom();
return (
<header
className={`
fixed top-0 w-full bg-white shadow
transition-transform duration-300
${pinned ? 'translate-y-0' : '-translate-y-full'}
`}
>
<nav>{/* Navigation links */}</nav>
</header>
);
}
With custom thresholds
const { pinned } = useHeadroom({
fixedAt: 100, // Start hiding after 100px
tolerance: 10, // Require 10px scroll delta
});
return (
<header className={pinned ? 'visible' : 'hidden'}>
Navigation
</header>
);
const { pinned, scrollY } = useHeadroom({ fixedAt: 80 });
const isScrolled = scrollY > 80;
return (
<header
className={`
fixed top-0 transition-all
${pinned ? 'translate-y-0' : '-translate-y-full'}
${isScrolled ? 'bg-white shadow-lg' : 'bg-transparent'}
`}
>
Navigation
</header>
);
Animated with framer-motion
import { motion } from 'framer-motion';
function AnimatedNav() {
const { pinned } = useHeadroom({ fixedAt: 60 });
return (
<motion.header
className="fixed top-0 w-full"
animate={{ y: pinned ? 0 : -100 }}
transition={{ duration: 0.3 }}
>
{/* Navbar content */}
</motion.header>
);
}
With opacity transition
const { pinned, scrollY } = useHeadroom();
const opacity = scrollY > 50 ? 1 : 0.9;
return (
<header
className={pinned ? 'visible' : 'hidden'}
style={{
opacity,
transition: 'opacity 0.3s, transform 0.3s',
}}
>
Navigation
</header>
);
Show on hover at top
function StickyHeader() {
const { pinned, scrollY } = useHeadroom({ fixedAt: 80 });
const [hovering, setHovering] = useState(false);
const isVisible = pinned || (scrollY > 80 && hovering);
return (
<div
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
className="fixed top-0 w-full"
>
<header
className={`
transition-transform duration-300
${isVisible ? 'translate-y-0' : '-translate-y-full'}
`}
>
Navigation
</header>
</div>
);
}
Notes
- The navbar is always visible (
pinned: true) when scrolled near the top (within fixedAt)
- Scrolling up shows the navbar, scrolling down hides it
- The
tolerance prevents jittery behavior from small scroll movements
- Uses passive scroll listeners for optimal performance
Type Definitions
interface UseHeadroomReturn {
pinned: boolean;
scrollY: number;
}
interface UseHeadroomOptions {
fixedAt?: number;
tolerance?: number;
}
function useHeadroom(options?: UseHeadroomOptions): UseHeadroomReturn;