JavaScript provides methods such as scrollTo() and scrollBy() for scrolling to a target position. These methods are often used with the behavior property set to "smooth" to animate the scroll smoothly to the destination. A common example is smooth scrolling to the relevant section when a table of contents item is clicked.
The main drawback of smooth scrolling is that it has been difficult to run code after scrolling finishes. With Programmatic Scroll Promises, available from Chrome and Edge 150, scroll completion that previously had to be detected with the scrollend event or setTimeout() can be controlled with a Promise.
Handling scroll completion with Promise
A Promise is an object for controlling asynchronous processing. It is commonly used to handle the completion or failure of operations that take time, such as fetching data from a server or running animations. Combined with await, a Promise lets you wait until a process finishes before running the next step. For more about Promise and await, see the article JavaScriptのモダンな書き方- ES2017〜ES2018のawait・async, includes(), padStart()等を解説.
Just await the scroll
Until now, methods such as scrollTo() were specified to return undefined. With Programmatic Scroll Promises, scroll-related methods return a Promise. This makes it possible to control the sequence of waiting for scrolling to finish and then running the next process.
The following code waits for smooth scrolling with scrollTo() to finish by using await, then displays a message. Since all it requires is adding await, the implementation is very straightforward.
Note: The following demos require Chrome or Edge 150 or later.
// Omitted
const message = document.querySelector("#message");
const onClick = async () => {
message.textContent = "";
// Wait for scrolling to finish with await
await window.scrollTo({
top: target.offsetTop,
behavior: "smooth",
});
// Display a message after scrolling finishes
message.textContent = "scroll end!";
};
Covered methods
The explainer explainers-by-googlers/promisify-scroll, which describes Programmatic Scroll Promises, lists the following methods as covered.
scroll(): Scrolls the page or an element to a specified positionscrollTo(): Equivalent toscroll()scrollBy(): Scrolls by a specified amount from the current positionscrollIntoView(): Scrolls the page or parent container so that the element on which the method is called enters the visible area
As of July 2026, in the author’s environment, scrollIntoView() resolved its Promise before scrolling had completed. Future improvements are expected. Test environment: Chrome 150.0.7871.46 / macOS 26.5.1 / MacBook Air M1, 2020
Why is this useful?
What are the benefits of controlling scrolling with Promise? Consider a case where clicking a table of contents item scrolls to the relevant section and then highlights the section heading. Assume the following requirements.
- When a table of contents item is clicked, smoothly scroll to the section and animate a highlight on the heading
- Do not animate the highlight for scrolling triggered by anything other than clicking the table of contents
Previous approaches and their issues
Monitoring the scrollend event
You need to determine whether the scroll was triggered by clicking the table of contents or by something else. The scrollend event tells you that scrolling has completed on the page as a whole or on a specific DOM element, but it does not tell you what triggered that scroll.
The following code branches the process with a shouldHighlight flag to determine what caused the scroll. However, flag-based control can lead to unintended display states if you forget to turn the flag on or off. In a single-page application, forgetting to remove the event listener when navigating to another page can also cause unnecessary scrollend events to keep firing.
let shouldHighlight = false;
// Process when a table of contents item is clicked
const onClick = () => {
// Set the flag
shouldHighlight = true;
window.scrollTo({
top: targetSection.offsetTop,
behavior: "smooth",
});
};
window.addEventListener("scrollend", () => {
if (!shouldHighlight) {
// Do nothing unless the table of contents was clicked
return;
}
shouldHighlight = false;
highlightTitle(targetSection);
});
Using setTimeout()
After a table of contents item is clicked, setTimeout() waits for a fixed amount of time before running the animation. Because the scroll duration changes depending on the distance, the animation may start too early or too late.
const onClick = () => {
window.scrollTo({
top: section.offsetTop,
behavior: "smooth",
});
// Highlight after 0.5 seconds
window.setTimeout(() => {
highlightTitle(section);
}, 500);
};
Using Intersection Observer
When using Intersection Observer, the issue is the same as with scrollend. You still need to branch the process depending on whether the table of contents was clicked or the scroll was triggered by something else.
let shouldHighlight = false;
const observer = new IntersectionObserver((entries) => {
if (!shouldHighlight) {
// Do nothing unless the table of contents was clicked
return;
}
entries.forEach((entry) => {
if (entry.target === targetSection && entry.isIntersecting) {
// Highlight once the target intersects
shouldHighlight = false;
highlightTitle(targetSection);
}
});
});
For more about Intersection Observer, see the article JSでのスクロール連動エフェクトにはIntersection Observerが便利.
Using Programmatic Scroll Promises
When scroll completion can be controlled with a Promise, the process becomes simpler. There is no need to monitor unnecessary events.
const onClick = async (event) => {
// Omitted
await window.scrollTo({
top: section.offsetTop,
behavior: "smooth",
});
// Highlight after scrolling
highlightTitle(section);
};
Application: guide tour demo
This demo implements a guide tour that explains how to use an application. Programmatic Scroll Promises are a good fit for use cases where the UI scrolls to a specific element, displays an explanation, and then moves to the next element.
After scrolling finishes, the target section is highlighted. Applying CSS after scrolling completes naturally draws the user’s attention to the target section.
const showStep = async (stepIndex) => {
const step = steps[stepIndex];
const target = document.querySelector(step.selector);
// Omitted
await window.scrollTo({
behavior: "smooth",
top: centerY,
});
// Add is-active to the target after scrolling to highlight it
target.classList.add("is-active");
};
The demo also includes a process that detects the end of the tour and re-enables the “Start tour” button. By waiting until scrolling finishes before re-enabling the button, duplicate execution during the tour can be avoided.
const endTour = async () => {
// Omitted
await window.scrollTo({
behavior: "smooth",
top: 0,
});
// Enable the button after scrolling
startButton.disabled = false;
};
When scrolling is interrupted
With Programmatic Scroll Promises, if scrolling is interrupted, the Promise is resolved immediately and the next process always runs. In the guide tour, repeatedly pressing the “Next” button can result in multiple panels being highlighted.
To avoid this, use the value returned from the scroll method. When scrolling is interrupted, the returned value’s interrupted property becomes true, so you can branch the process and skip the next step.
const showStep = async (stepIndex) => {
// Omitted
const result = await window.scrollTo({
behavior: "smooth",
top: centerY,
});
if (result.interrupted) {
// Do nothing if scrolling was interrupted
return;
}
// Activate after scrolling
target.classList.add("is-active");
};
Browser support
Programmatic Scroll Promises are available in Chrome and Edge 150 or later, as of July 2026.
Reference: Can I use…
Conclusion
This article introduced Programmatic Scroll Promises. Being able to control scroll completion with a Promise makes the code much simpler and removes the need for extra flags or event monitoring.
Support in browsers other than Chrome and Edge is expected in the future, and this feature may become a standard part of scroll-related implementation.

