NavigateEvent: intercept() method

Baseline 2026 *
Newly available

Since January 2026, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

* Some parts of this feature may have varying levels of support.

The intercept() method of the NavigateEvent interface intercepts this navigation, turning it into a same-document navigation to the destination URL.

Syntax

js
intercept()
intercept(options)

Parameters

options Optional

An options object containing the following properties:

handler Optional

A callback function that defines what the navigation handling behavior should be; it returns a promise. This function will run after the currentEntry property has been updated.

precommitHandler Optional

A callback function that defines any behavior that should occur just before the navigation has committed; it accepts a NavigationPrecommitController object as an argument and returns a promise. This function will run before the currentEntry property has been updated.

focusReset Optional

Defines the navigation's focus behavior. This may take one of the following values:

after-transition

Once the promise returned by your handler function resolves, the browser will focus the first element with the autofocus attribute, or the <body> element if no element has autofocus set. This is the default value.

manual

Disable the default behavior.

scroll Optional

Defines the navigation's scrolling behavior. This may take one of the following values:

after-transition

Allow the browser to handle scrolling, for example by scrolling to the relevant fragment identifier if the URL contains a fragment, or restoring the scroll position to the same place as last time if the page is reloaded or a page in the history is revisited. This is the default value.

manual

Disable the default behavior.

Return value

None (undefined).

Exceptions

InvalidStateError DOMException

Thrown if the current Document is not yet active, or if the navigation has been cancelled.

SecurityError DOMException

Thrown if:

Description

The intercept() method is used to implement same-document (SPA) navigation behavior when a navigation occurs; for example, when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using History.pushState(), Window.location, etc.).

It does this via a couple of different callbacks, handler() and precommitHandler().

Handling immediate navigations with handler()

The handler() callback is run in response to a committed navigation. It will run after the currentEntry property has been updated, meaning that a new URL is shown in the browser UI and the history is updated with a new entry.

A typical example looks like this, enabling specific content to be rendered and loaded in response to a certain navigation:

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      async handler() {
        // Fetch the new content and display when ready
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }

  // Include multiple conditions for different page types here, as needed
});

handler() should be used to implement navigation behavior where the navigation is committed to: the user should be shown something new.

Handling precommit actions with precommitHandler()

However, you might also wish to modify or cancel in-flight navigation, or to perform work while the navigation is ongoing and before it is committed. This kind of scenario can be dealt with using the precommitHandler() callback, which runs before the currentEntry property has been updated and the browser UI shows the new location.

For example, if the user navigates to a restricted page and is not signed in, you may want to redirect the browser to a sign-in page. This might be handled like so:

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/restricted/") && !userSignedIn) {
    event.intercept({
      async precommitHandler(controller) {
        controller.redirect("/signin/", {
          state: "signin-redirect",
          history: "push",
        });
      },
    });
  }
});

This pattern is simpler than the alternative of canceling the original navigation and starting a new one to the redirect location, because it avoids exposing the intermediate state. For example, only one navigatesuccess or navigateerror event fires, and if the navigation was triggered by a call to Navigation.navigate(), the promise only fulfills once the redirect destination is reached.

The precommitHandler() callback takes a NavigationPrecommitController object as an argument, which contains a redirect() method. The redirect() method takes two parameters — a string representing the URL to redirect to, and an optional options object than can specify state and history behavior.

precommitHandler() generally handles any modifications to the navigation behavior that are required before the destination URL is actually displayed in the browser, cancelling or redirecting it somewhere else as required.

Note: Because precommitHandler() can be used to cancel navigations, it will only work as expected when the event's Event.cancelable property is true. Calling intercept() with a precommitHandler() on a non-cancelable event results in a SecurityError being thrown.

Scheduling post-commit actions in precommitHandler()

As we saw above, you can specify a handler() callback in the object passed to the intercept() method in order to preform actions after a navigation is committed. This approach works well if the actions required after commit do not depend on any actions run in the pre-commit phase. If they do, then you can use NavigationPrecommitController.addHandler() in precommitHandler() to dynamically add a handler that will run after the navigation commits.

For example, consider this code that extends the previous example for redirecting a logged-out user to a sign-in page. The code uses addHandler() to add a post-commit handler callback that shows a message explaining the redirect reason. Note that the handler only runs for the specific case of a redirect to the sign-in page.

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/restricted/") && !userSignedIn) {
    event.intercept({
      async precommitHandler(controller) {
        controller.redirect("/signin/", {
          state: "signin-redirect",
          history: "push",
        });

        // Use addHandler to trigger logic once the /signin/ page commits
        controller.addHandler(() => {
          showMessage("Please sign in to view that content.");
        });
      },
    });
  }
});

Responding to navigation success or failure

When the promises returned by the intercept() handler functions fulfill, the Navigation object's navigatesuccess event fires, allowing you to run cleanup code after a successful navigation has completed. If those promises reject, meaning the navigation has failed, navigateerror fires instead, allowing you to gracefully handle the failure case.

There is also a finished property on the return value of navigation methods (such as Navigation.navigate()), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases.

Interaction between precommitHandler() and handler()

Both precommitHandler() and handler() callbacks can be included inside the same intercept() call. In such cases, the order of operations is as follows:

  1. First, the precommitHandler() handler runs.

    • When the precommitHandler() promise fulfills, the navigation commits.
    • If the precommitHandler() rejects, the navigateerror event fires, the committed and finished promises reject, and the navigation is cancelled.
  2. When the navigation commits, a new NavigationHistoryEntry is created for the navigation, and its committed promise fulfills.

  3. Next, the handler() promise runs.

    • When the handler() promise fulfills and the navigatesuccess event fires, the navigation finished promise fulfills as well, to indicate the navigation is finished.
    • If handler() rejects, the navigateerror event fires, the finished promise rejects, and the navigation is canceled.

Note that the above process is upheld even across multiple intercept() calls on the same NavigateEvent, and for handler() callbacks added in the precommitHandler(). All precommitHandler() callbacks are called first, and when all of them resolve, the navigation commits, and all the handler() callbacks are called.

Controlling focus behavior

By default, after a navigation handled using intercept() has occurred, the document focus will reset to the first element in the DOM with an autofocus attribute set, or otherwise to the <body> element, if no autofocus attribute is set. If you want to override this behavior, to manually implement a more accessible focus position on navigation (for example, the new top-level heading), you can do so by setting the focusReset option to manual.

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      focusReset: manual,
      async handler() {
        // Fetch the new content and display when ready
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
        // Handle page focus with a custom function
        setPageFocus();
      },
    });
  }
});

Controlling scroll behavior

After an intercept() navigation has completed, the following scrolling behavior occurs:

  • For push and replace navigations (see Navigation.navigate()), the browser will attempt to scroll to the fragment given by event.destination.url. If there is no fragment available, it will reset the scroll position to the top of the page.
  • For traverse and reload navigations, the behavior is similar to push and replace navigations, but the browser delays its scroll restoration logic until the intercept() promise fulfills. It will perform no scroll restoration if the promise rejects. If the user has scrolled during the transition then no scroll restoration will be performed.

If you want to turn this behavior off, you can do so by setting the scroll option to manual.

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      scroll: manual,
      async handler() {
        // Fetch the new content and display when ready
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
        // Handle scroll behavior with a custom function
        setScroll();
      },
    });
  }
});

If you want to manually trigger the default scrolling behavior described earlier (maybe you want to reset the scroll position to the top of the page early, before the full navigation has finished), you can do so by calling NavigateEvent.scroll().

Examples

Handling a navigation using intercept()

js
navigation.addEventListener("navigate", (event) => {
  // Exit early if this navigation shouldn't be intercepted,
  // e.g. if the navigation is cross-origin, or a download request
  if (shouldNotIntercept(event)) return;

  const url = new URL(event.destination.url);

  if (url.pathname.startsWith("/articles/")) {
    event.intercept({
      async handler() {
        // The URL has already changed, so show a placeholder while
        // fetching the new content, such as a spinner or loading page
        renderArticlePagePlaceholder();

        // Fetch the new content and display when ready
        const articleContent = await getArticleContent(url.pathname);
        renderArticlePage(articleContent);
      },
    });
  }
});

Using focusReset and scroll

Form submission can be detected by querying for the NavigateEvent.formData property. The following example turns any form submission into one which stays on the current page. In this case, you don't update the DOM, so you can cancel any default reset and scroll behavior using focusReset and scroll.

js
navigation.addEventListener("navigate", (event) => {
  if (event.formData && event.canIntercept) {
    // User submitted a POST form to a same-domain URL
    // (If canIntercept is false, the event is just informative:
    // you can't intercept this request, although you could
    // likely still call .preventDefault() to stop it completely).

    event.intercept({
      // Since we don't update the DOM in this navigation,
      // don't allow focus or scrolling to reset:
      focusReset: "manual",
      scroll: "manual",
      async handler() {
        await fetch(event.destination.url, {
          method: "POST",
          body: event.formData,
        });
        // You could navigate again with {history: 'replace'} to
        // change the URL here, which might indicate "done"
      },
    });
  }
});

Specifications

Specification
HTML
# dom-navigateevent-intercept-dev

Browser compatibility

desktop mobile
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
intercept
Chrome – No support
Chrome 102 – 107 (Release date: 2022-05-24)
altname
footnote Removed in 108 and later
altname Alternate name: transitionWhile
Chrome – Full support
Chrome 105 (Release date: 2022-09-02)
footnote Full support
Edge – No support
Edge 102 – 107 (Release date: 2022-05-31)
altname
footnote Removed in 108 and later
altname Alternate name: transitionWhile
Edge – Full support
Edge 105 (Release date: 2022-09-01)
footnote Full support
Firefox – Full support
Firefox 147 (Release date: 2026-01-13)
footnote Full support
Opera – No support
Opera 88 – 93 (Release date: 2022-06-08)
altname
footnote Removed in 94 and later
altname Alternate name: transitionWhile
Opera – Full support
Opera 91 (Release date: 2022-09-14)
footnote Full support
Safari – Full support
Safari 26.2 (Release date: 2025-12-12)
footnote Full support
Chrome Android – No support
Chrome Android 102 – 107 (Release date: 2022-05-24)
altname
footnote Removed in 108 and later
altname Alternate name: transitionWhile
Chrome Android – Full support
Chrome Android 105 (Release date: 2022-09-02)
footnote Full support
Firefox for Android – Full support
Firefox for Android 147 (Release date: 2026-01-13)
footnote Full support
Opera Android – No support
Opera Android 70 – 72 (Release date: 2022-06-29)
altname
footnote Removed in 73 and later
altname Alternate name: transitionWhile
Opera Android – Full support
Opera Android 72 (Release date: 2022-10-21)
footnote Full support
Safari on iOS – Full support
Safari on iOS 26.2 (Release date: 2025-12-12)
footnote Full support
Samsung Internet – No support
Samsung Internet 19 – 20 (Release date: 2022-11-01)
altname
footnote Removed in 21 and later
altname Alternate name: transitionWhile
Samsung Internet – Full support
Samsung Internet 20 (Release date: 2023-02-10)
footnote Full support
WebView Android – No support
WebView Android 102 – 107 (Release date: 2022-05-24)
altname
footnote Removed in 108 and later
altname Alternate name: transitionWhile
WebView Android – Full support
WebView Android 105 (Release date: 2022-09-02)
footnote Full support
WebView on iOS – Full support
WebView on iOS 26.2 (Release date: 2025-12-12)
footnote Full support
precommitHandler option
Chrome – Full support
Chrome 141 (Release date: 2025-09-30)
footnote Full support
Edge – Full support
Edge 141 (Release date: 2025-10-03)
footnote Full support
Firefox – Full support
Firefox 147 (Release date: 2026-01-13)
footnote Full support
Opera – Full support
Opera 125 (Release date: 2025-12-04)
footnote Full support
Safari – No support
Safari
footnote No support
Chrome Android – Full support
Chrome Android 141 (Release date: 2025-09-30)
footnote Full support
Firefox for Android – Full support
Firefox for Android 147 (Release date: 2026-01-13)
footnote Full support
Opera Android – Full support
Opera Android 93 (Release date: 2025-11-25)
footnote Full support
Safari on iOS – No support
Safari on iOS
footnote No support
Samsung Internet – No support
Samsung Internet
footnote No support
WebView Android – Full support
WebView Android 141 (Release date: 2025-09-30)
footnote Full support
WebView on iOS – No support
WebView on iOS
footnote No support

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support
No support
No support
Uses a non-standard name
Has more compatibility info.

See also