Skip to content
Esc
navigateopen⌘Jpreview

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
  • v11
  • MIT
  • TypeScript
  • ~1 kB gzipped
  • React 17+
observerlive
threshold
Hero
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.

useInView
const { ref, inView } = useInView({  threshold: 0.5,});<section ref={ref}>  {inView ? "In view" : "Waiting"}</section>
  1. const { ref, inView } = useInView({  threshold: 0.5,});<section ref={ref}>  {inView ? "In view" : "Waiting"}</section>
  2. const ref = useOnInView(  (inView, entry) => {    track("seen", entry.target);  },  { threshold: 1, triggerOnce: true },);
  3. <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.

card-01waiting
card-02waiting
card-03waiting
card-04waiting
card-05waiting
card-06waiting
card-07waiting
card-08waiting

Try it yourself.

Change the threshold and scroll the custom root. Same hook, wired to live controls and a live readout.

See when an element becomes visible
Step 2 · Scroll this panel to reveal the observed card ↓
Design systems that travelA small feed item
A quiet note on shippingA small feed item
Small details, deliberately timedA small feed item
A scroll worth observingA small feed item
A detail worth loadingA small feed item
One more thing to noticeA small feed item
QueuedWatching
Feature card is waiting

Scroll until enough of this card is visible.

Waiting for visibility
Where interaction beginsA small feed item
A follow-up for the readerA small feed item
The next item is already waitingA small feed item

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-observer

Testing? The package ships a mock for the Intersection Observer so your suites stay deterministic.