Ponyfill

This package ponyfills new features in the Element.scrollIntoView API. Including features in the non-standard Element.scrollIntoViewIfNeeded API. The CSS working group has an open proposal under discussion for extending scrollIntoView. This library exposes scrollMode: "if-needed" as a library option; it is not an adopted standard.

Minimal

The core package scroll-into-view-if-needed is small, using a shared geometry engine. That is why behavior: "smooth" is only ponyfilled if you use the add-on package smooth-scroll-into-view-if-needed. You don't need the add-on if native smooth scrolling is enough.

Customizable

A couple of options not in the spec by the CSS working group helps customize scrolling to your needs. Avoid scrolling unwanted parent elements by using the boundary: Element option. Pass a function to behavior to customize exactly how elements are scrolled when smooth isn't enough.

Scrolling if needed

The library’s scrollMode: 'if-needed' option checks whether the target fits within the viewing region. The updated engine accounts for scroll margins and scroll padding at each ancestor.

By default the browser controls the scrolling when behavior: 'smooth' (unless you opt in to the ponyfill). Note there's browser differences with native smooth scrolling, like Chrome vs FireFox in this CodePen.

import scrollIntoView from 'smooth-scroll-into-view-if-needed';

scrollIntoView(node, {
  behavior: 'smooth',
  scrollMode: 'if-needed',
});
Scroll to 
A
B
C
D
E
F
G

Scroll alignment

The position options for both block and inline are supported. Mix and match to your heart's content.

Usually block aligns vertically, while inline aligns horizontally. It depends on the writing-mode.

import scrollIntoView from 'scroll-into-view-if-needed';

scrollIntoView(node, {
  behavior: 'smooth',
  block: 'center',
  inline: 'center',
});
Scroll to 
1
2
3
4
5
6
7
8
9

Limit propagation

Boundaries are good, that's what people keep saying. If you want some elements to scroll into view, but not all of the parents then boundary is the answer.

boundary is a library extension. CSSOM View now definescontainer: 'nearest' for stopping after the nearest scroll container, with 'all' as the default. Native support varies; the accompanying engine update adds this option to the ponyfill.

import scrollIntoView from 'scroll-into-view-if-needed';

scrollIntoView(node, {
  behavior: 'smooth',
  block: 'end',
  boundary: document.getElementById('example-boundary'),
});
Scroll to 
😁
🤯
😅
🤔
🤩
🤨
😲

Current scrolling standards

Standards and browser support checked 11 November 2026.

Space around the target

Set scroll-margin on the target and scroll-paddingon the scroll container to leave room for sticky headers or other content. These properties affect scrolling into view even without scroll snapping. The updated engine includes both, plus percentage padding and CSS math.

.scroller { scroll-padding-top: 4rem; }
.target { scroll-margin-block: 1rem; }

Wait for native scrolling

The CSSOM View draft specifies promises for scroll(), scrollTo(),scrollBy(), and scrollIntoView(). They resolve to{ interrupted: boolean }. The updated ponyfill combines native promises when available; older browsers still return undefined.

const completion = scrollIntoView(target, {
  behavior: 'smooth',
})
if (completion) {
  const { interrupted } = await completion
}

Awaiting undefined does not wait for a legacy native smooth scroll.

Keep focus in your control

HTML defines focus() separately. It returns undefined and accepts preventScroll. Proposed focus scroll options remain under discussion.

target.focus({ preventScroll: true })
scrollIntoView(target, {
  scrollMode: 'if-needed',
  block: 'nearest',
})

The if-needed proposal remains open and currently uses a member named scroll. scrollMode, custom behavior callbacks, and boundaryare library extensions. See the standards review for compatibility, test coverage, and release order.

Custom transition: popmotion example

If you want a different easing, duration or another creative direction you can pass a function to behavior.

Just like boundary this is not in the spec.

import scrollIntoView from 'scroll-into-view-if-needed';
import Styler from 'stylefire';
import { animate } from 'popmotion';

scrollIntoView(node, {
  behavior: (instructions) => {
    const [{ el, left }] = instructions;
    const styler = Styler(el);

    animate({
      from: el.scrollLeft,
      to: left,
      type: 'spring',
      onUpdate: (left) => styler.set('scrollLeft', left),
    });
  },
  inline: 'center',
});
Scroll to 
🌎
🌍
🌏