758 words
4 minutes

scrollIntoView Is Not a Function: Fix NodeList, null, and Framework Refs

2026-08-09
Frontend
JavaScript
/
Frontend
/
Troubleshooting
/
CSS

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:

// React
const headingRef = useRef<HTMLHeadingElement>(null);
headingRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
// Vue
const 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:

  • Element or HTMLElement: call scrollIntoView(); if it still behaves oddly, inspect the scroll container and CSS.
  • NodeList: select one item with .item(0) or iterate intentionally.
  • null or undefined: 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:

MDN: Element.scrollIntoView()

MDN: Element.querySelectorAll()

MDN: Element.querySelector()

scrollIntoView Is Not a Function: Fix NodeList, null, and Framework Refs
https://laplusda.com/en/posts/javascript-scrollintoview-not-a-function/
Author
Zero
Published at
2026-08-09
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.