react-scroll-into-view: The One-Component Scroll Whisperer
You have a long page. You have a button that says "Jump to FAQ." You want clicking that button to smoothly scroll to the FAQ section. The native browser API for this is Element.scrollIntoView(), and it works great. But wiring up a querySelector call, a click handler, and scroll options every single time you need this pattern gets old fast. react-scroll-into-view wraps all of that boilerplate into a single declarative component with one required prop.
react-scroll-into-view is a thin React wrapper around the browser's built-in scrollIntoView API. You wrap any clickable content in a ScrollIntoView component, pass it a CSS selector pointing at the target element, and the component handles the rest. At 1.3 kB gzipped with zero runtime dependencies, it is about as lightweight as a scroll solution can get.
What It Brings to the Table
- Declarative scrolling with a single component and a CSS selector
- Smooth or instant scrolling via the
smoothprop - Full native options through the
scrollOptionsprop, giving you control overbehavior,block, andinlinealignment - Zero dependencies beyond React 18+ as a peer dependency
- TypeScript support out of the box
- Keyboard accessible with
role="button"and keyboard event handling as of v2.3.0 - Selector-based targeting means the scroll target does not need to live in the same component tree
Getting Started
Install via npm:
npm install react-scroll-into-view
or yarn:
yarn add react-scroll-into-view
The package requires React 18 or later as a peer dependency.
Click, Scroll, Done
Jumping to an Element
The simplest use case is a button that scrolls to an element identified by its ID:
import ScrollIntoView from 'react-scroll-into-view';
function TableOfContents() {
return (
<nav>
<ScrollIntoView selector="#introduction">
<button>Introduction</button>
</ScrollIntoView>
<ScrollIntoView selector="#features">
<button>Features</button>
</ScrollIntoView>
<ScrollIntoView selector="#pricing">
<button>Pricing</button>
</ScrollIntoView>
</nav>
);
}
Each ScrollIntoView component renders a wrapper <div> that listens for clicks. When clicked, it finds the target element using document.querySelector with the selector you provided and calls scrollIntoView() on it. Smooth scrolling is enabled by default.
Back to Top
A "back to top" button is a one-liner. Point the selector at body and you are done:
import ScrollIntoView from 'react-scroll-into-view';
function BackToTop() {
return (
<ScrollIntoView selector="body" smooth>
<button className="back-to-top">Back to Top</button>
</ScrollIntoView>
);
}
If you want instant scrolling instead of the smooth animation, set smooth={false}:
<ScrollIntoView selector="body" smooth={false}>
<button>Jump to Top</button>
</ScrollIntoView>
Combining with a Click Handler
Need to do something besides scrolling when the button is clicked? The onClick prop lets you run additional logic alongside the scroll:
import ScrollIntoView from 'react-scroll-into-view';
function NavLink() {
const handleClick = () => {
analytics.track('nav_click', { section: 'faq' });
};
return (
<ScrollIntoView selector="#faq" onClick={handleClick}>
<span className="nav-link">FAQ</span>
</ScrollIntoView>
);
}
The scroll and your callback both fire on the same click event.
Fine-Tuning the Scroll
Controlling Viewport Alignment
The scrollOptions prop passes options directly to the native scrollIntoView API. This gives you precise control over where the target element lands in the viewport:
import ScrollIntoView from 'react-scroll-into-view';
function CenteredScroll() {
return (
<ScrollIntoView
selector="#highlight-section"
scrollOptions={{
behavior: 'smooth',
block: 'center',
inline: 'nearest',
}}
>
<button>Show Highlight</button>
</ScrollIntoView>
);
}
The block option controls vertical alignment: 'start' puts the element at the top of the viewport, 'center' centers it, 'end' puts it at the bottom, and 'nearest' scrolls the minimum distance needed. The inline option does the same for horizontal alignment.
Note that scrollOptions takes precedence over the smooth and alignToTop convenience props. If you pass scrollOptions, use behavior: 'smooth' inside it rather than the standalone smooth prop.
Using Any CSS Selector
Because the component uses document.querySelector under the hood, you are not limited to ID selectors. Any valid CSS selector works:
import ScrollIntoView from 'react-scroll-into-view';
function ScrollToFirstError() {
return (
<ScrollIntoView
selector=".form-field--error"
scrollOptions={{ behavior: 'smooth', block: 'center' }}
>
<button>Go to First Error</button>
</ScrollIntoView>
);
}
This is particularly useful for dynamic content where you might want to scroll to the first element matching a class, a data attribute, or any other selector pattern. The selector is evaluated at click time, so it always finds the current state of the DOM.
Styling the Wrapper
The component renders a <div> wrapper around its children. You can style it with className or style to make it fit your layout:
import ScrollIntoView from 'react-scroll-into-view';
function InlineScrollLink() {
return (
<ScrollIntoView
selector="#terms"
className="inline-link"
style={{ display: 'inline', cursor: 'pointer' }}
>
<span>Terms and Conditions</span>
</ScrollIntoView>
);
}
This is worth keeping in mind because the wrapper <div> is a block-level element by default. If you are placing the component inline with text, you will want to override the display.
When to Use a Library vs. the Native API
The honest truth is that react-scroll-into-view is a thin convenience wrapper. You could write the same behavior with a few lines of code:
function scrollTo(selector: string) {
document.querySelector(selector)?.scrollIntoView({ behavior: 'smooth' });
}
So when does the library earn its keep? When you are reaching for this pattern repeatedly. If your page has a table of contents, a back-to-top button, a "skip to main content" link, and a "scroll to errors" feature, writing and maintaining four separate querySelector + scrollIntoView + click handler setups is tedious. The declarative component approach keeps things consistent and readable, especially across a team.
The selector-based approach also has a real advantage over refs: the scroll target does not need to be in the same component tree, or even rendered by React at all. You can scroll to an element in a third-party widget, a server-rendered section, or anything else that exists in the DOM.
The v2.3.0 Accessibility Story
The most recent release added two small but meaningful accessibility improvements. The wrapper <div> now gets role="button", which tells screen readers that the element is interactive. It also handles onKeyDown events, so keyboard users can trigger the scroll with the keyboard rather than relying solely on mouse clicks.
These are the kinds of details that separate a production-ready component from a weekend project. If you are building accessible navigation, you want your scroll triggers to behave like proper interactive elements.
Smooth Landing
react-scroll-into-view is not trying to be a full-featured scroll navigation system. It does not track active sections, it does not highlight the current scroll position, and it does not manage route changes. If you need those features, look at react-scroll or build a custom solution with Intersection Observer.
What it does offer is a single component that does one thing well: click a thing, scroll to another thing. At 1.3 kB with zero dependencies, TypeScript support, and recent accessibility improvements, it is a clean fit for any React project where you just need a button that takes the user somewhere on the page.