Know the moment an element meets the viewport.
A tiny, fully-typed React adapter for the Intersection Observer API. Reveal on scroll, lazy-load, track impressions, and build infinite lists with one hook and about a kilobyte.
$ npm i react-intersection-observer- section
- Hero
- intersectionRatio
- 1.00
- inView
- true
- impressions
- 0
This section revealed itself.
Every card below waited off-screen and animated in as it crossed the threshold, with the same useInView you would ship. It fires once and then leaves the DOM alone.
Hooks or component
useInView for state, useOnInView for effects, and <InView> for render props or a wrapper.
About a kilobyte
Around 1 kB gzipped per API. Tree-shakeable, so you ship only what you import.
Matches the native API
The same threshold, rootMargin, and root options you already know from IntersectionObserver.
Shared instances
Elements with identical options reuse one observer, so thousands of targets stay cheap.
Typed to the core
Written in TypeScript. Options, return values, and entries are typed, with no extra @types.
Ready to test
A drop-in mock for the Intersection Observer keeps Vitest and Jest suites deterministic.
useInView({ triggerOnce: true })drives every reveal on this page.Three APIs, one observer.
Pick the shape that fits your component. The panel follows whichever you are reading. That is useInView doing scrollspy, right here. Tap a card to pin one.
const { ref, inView } = useInView({ threshold: 0.5,});<section ref={ref}> {inView ? "In view" : "Waiting"}</section>const { ref, inView } = useInView({ threshold: 0.5,});<section ref={ref}> {inView ? "In view" : "Waiting"}</section>const ref = useOnInView( (inView, entry) => { track("seen", entry.target); }, { threshold: 1, triggerOnce: true },);<InView as="div" threshold={0.2} triggerOnce> {({ ref, inView }) => ( <div ref={ref}> {inView ? "Loaded" : "Placeholder"} </div> )}</InView>
Fire once, exactly when seen.
useOnInView runs your callback without a hook-owned re-render. It is the right tool for impressions, prefetching, and logging. Each tile below logs itself the first time it is fully visible.
Try it yourself.
Change the threshold and scroll the custom root. Same hook, wired to live controls and a live readout.
Add a kilobyte. Ship the viewport.
Install, attach a ref, and read inView. Thresholds, roots, and margins are there when you need them.
$ npm i react-intersection-observerTesting? The package ships a mock for the Intersection Observer so your suites stay deterministic.