scrollIntoView Is Not a Function: Fix NodeList, null, and Framework Refs
scrollIntoView() belongs to a single DOM Element. The error scrollIntoView is not a function means the value before the dot is not that element. In practice, it is usually a NodeList from querySelectorAll(), null from a selector that matched nothing, or a framework ref wrapper whose element is stored in another property.
Inspect the value first, then call the method on the actual element:
const target = document.querySelector('[data-section="pricing"]');
if (target instanceof Element) { target.scrollIntoView({ behavior: 'smooth', block: 'start' });}This is a type and lifecycle problem, not a missing browser API. MDN documents scrollIntoView() as an Element method, so changing the scroll options cannot repair a receiver that is a list, wrapper, or empty result.
Fix querySelectorAll() returning a NodeList
querySelectorAll() always returns a static NodeList, even when only one element matches. A NodeList does not have the element method directly:
const headings = document.querySelectorAll('h2');
// Wrong: the receiver is a NodeList.headings.scrollIntoView();Choose one element before scrolling:
const headings = document.querySelectorAll('h2');const firstHeading = headings.item(0);
firstHeading?.scrollIntoView({ behavior: 'smooth', block: 'center',});Or use querySelector() when the selector should target one element:
document.querySelector('h2')?.scrollIntoView({ block: 'center' });If the intention is to scroll every matching element, iterate over the list explicitly. Usually that produces a series of scroll operations and the last element wins, so confirm that this is really the interaction you want:
for (const heading of document.querySelectorAll('h2')) { heading.scrollIntoView({ behavior: 'smooth' });}Handle querySelector() returning null
querySelector() returns null when there is no matching element. The resulting error is often Cannot read properties of null, but the fix is the same: check the selector, timing, and value before calling the method.
function scrollToSection(id) { const section = document.getElementById(id);
if (!section) { console.warn(`Section not found: ${id}`); return; }
section.scrollIntoView({ behavior: 'smooth', block: 'start' });}Common causes of a null result include a typo in the ID, a selector that runs before the component renders, and a route that is expected to contain a section but does not. Put the call after the DOM update that creates the target; do not paper over a missing element with a timeout unless the timeout is part of a documented rendering boundary.
Unwrap a framework ref before calling the API
Framework refs are containers around a DOM node, not DOM nodes themselves. Use the framework’s element property and keep the null check:
// Reactconst headingRef = useRef<HTMLHeadingElement>(null);
headingRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center',});// Vueconst heading = ref<HTMLElement | null>(null);
heading.value?.scrollIntoView({ behavior: 'smooth', block: 'center',});In Svelte or another component system, the same rule applies: keep a reference to the rendered element and call scrollIntoView() on that element after it exists. If the framework updates the DOM asynchronously, run the scroll after the update hook rather than during initial state setup.
Add a fixed-header offset with CSS
scrollIntoView() controls alignment, not a custom pixel offset argument. If a fixed header covers the target after scrolling, set scroll-margin-top on the target elements:
[data-scroll-target] { scroll-margin-top: 5rem;}Then keep the JavaScript focused on selecting the element and choosing an alignment:
document .querySelector('[data-scroll-target="pricing"]') ?.scrollIntoView({ behavior: 'smooth', block: 'start' });This preserves the browser’s scroll container behavior and keeps the header height in CSS, where responsive layout rules already belong. If the page has nested scroll containers, verify which container is moving before changing the offset.
Debug the receiver instead of the options
When the error is unclear, log the value and compare it with the expected shape:
const value = getTargetSomehow();
console.log({ value, constructor: value?.constructor?.name, isElement: value instanceof Element, isNodeList: value instanceof NodeList,});Then map the result to the fix:
ElementorHTMLElement: callscrollIntoView(); if it still behaves oddly, inspect the scroll container and CSS.NodeList: select one item with.item(0)or iterate intentionally.nullorundefined: fix the selector or run after the target renders.- Framework ref object: unwrap
.current,.value, or the equivalent element property. - A plain object: inspect the API that returned it; a data record is not a DOM node.
Use optional chaining for an expected missing target, but do not use it to hide a selector that must exist for the page to work. A warning or assertion is better for required navigation targets.
FAQ
Why does querySelectorAll() cause this error?
It returns a NodeList, not one element. Select an item from the list or iterate through it before calling the element method.
Does scrollIntoView() support an offset parameter?
No separate offset argument is needed for the common fixed-header case. Use scroll-margin-top on the target and an appropriate block option in the call.
Why does a React ref fail with scrollIntoView is not a function?
The ref object is not the DOM element. Call the method on ref.current after the element has rendered, and keep the null check for the initial render.
References:
Report a typo or broken link, or suggest a related topic.