---
title: Cancel in-progress view transitions on user interaction
description: >-
  Cancel in-progress view transitions when users interact with the page to
  prevent the blocked rendering period from creating INP delays.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/cancel-view-transitions-on-interaction
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/cancel-view-transitions-on-interaction.md
api_name: liquid
---

# Cancel in-progress view transitions on user interaction

Make sure that view transitions don't block user interactions by canceling them when users attempt to interact during the transition period, preventing INP delays.

***

## Why

The View Transitions API creates smooth, app-like transitions between pages. However, cross-document view transitions block rendering between the initial page screenshot and the server response. When users interact during this blocked rendering period, it creates large INP delays.

Performance profiling has shown that view transitions can significantly delay user interactions. The blocked rendering period prevents the browser from responding to user input, creating poor INP scores, especially when users click or tap during navigation transitions.

Chrome's performance best practices recommend canceling view transitions when users interact with the page to prevent these delays. Transitions improve perceived performance with an app-like experience and smooth visual continuity, but they can hurt measured performance (INP) if not implemented carefully.

***

## How

### Opt in to cross-document transitions with `@view-transition`

Cross-document view transitions don't happen unless both documents opt in with an `@view-transition` at-rule. Add it to your theme's stylesheet, which every page loads:

## assets/base.css

```css
@view-transition {
  navigation: auto;
}
```

The opt-in is checked per document: the outgoing document's rule gates `pageswap`, and the incoming document's rule gates `pagereveal`. Without it, `event.viewTransition` is always `null` and the cancellation code in the next section never runs.

### Cancel transitions on user interaction

Add event listeners that cancel the view transition when users interact:

```javascript
function cancelOnInteraction(event) {
  const viewTransition = event.viewTransition;
  if (!viewTransition) return;


  ["pointerdown", "keydown"].forEach((eventType) => {
    document.addEventListener(
      eventType,
      () => {
        viewTransition.skipTransition();
      },
      { once: true }
    );
  });
}


// Outgoing page: covers the blocked rendering period.
window.addEventListener("pageswap", cancelOnInteraction);


// Incoming page: covers the transition animation itself.
window.addEventListener("pagereveal", cancelOnInteraction);
```

**Caution:**

`pageswap` and `pagereveal` fire on `window`, not on the Navigation API's `navigation` object. Registering them anywhere else silently does nothing, so the transition is never canceled.

Use both `pointerdown`, which captures mouse clicks and touch taps, and `keydown`, which captures keyboard interactions. Handle `pagereveal` as well as `pageswap`: `pageswap` fires on the outgoing document, but the transition animates on the incoming one.

The `{ once: true }` option makes sure that listeners fire only once per transition, provides automatic cleanup without a manual `removeEventListener`, and prevents memory leaks.

### Use view transitions selectively

Apply view transitions strategically, not universally.

Good use cases:

* Product card to product page navigation.
* Image gallery browsing.
* Variant image switching.
* Collection to product navigation.

Avoid them for:

* Every navigation. Too aggressive.
* Form submissions.
* Checkout flows.
* Admin and account pages.
* External links.

### Respect user motion preferences

Always check for the reduced motion preference:

```javascript
function userPrefersReducedMotion() {
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}


if (document.startViewTransition && !userPrefersReducedMotion()) {
  // Apply view transition
}
```

### Provide a fallback for unsupported browsers

Support differs between the two kinds of view transition:

| Transition type | Chrome and Edge | Safari | Firefox |
| - | - | - | - |
| Cross-document (`@view-transition`, `pageswap`, `pagereveal`) | 126 or later | 18.2 or later | Not supported |
| Same-document (`document.startViewTransition()`) | 111 or later | 18 or later | 144 or later |

Always provide a fallback:

```javascript
if (document.startViewTransition) {
  // Use view transition
} else {
  // Standard navigation
  window.location = href;
}
```

### Define CSS animations

```css
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}


@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.01s;
  }
}
```

***

## Examples

### Full implementation with cancellation

```javascript
class ViewTransitionManager {
  constructor() {
    const handle = (event) => this.handleViewTransition(event);


    window.addEventListener("pageswap", handle);
    window.addEventListener("pagereveal", handle);
  }


  handleViewTransition(event) {
    const viewTransition = event.viewTransition;
    if (!viewTransition) return;


    const cancelTransition = () => {
      viewTransition.skipTransition();
    };


    document.addEventListener("pointerdown", cancelTransition, { once: true });
    document.addEventListener("keydown", cancelTransition, { once: true });


    viewTransition.finished.finally(() => {
      document.removeEventListener("pointerdown", cancelTransition);
      document.removeEventListener("keydown", cancelTransition);
    });
  }
}


new ViewTransitionManager();
```

### Conditional transitions based on context

Don't try to drive a navigation from `document.startViewTransition()`. That method is same-document only: passing it a navigation callback produces a roughly 250 ms non-interactive cross-fade of the unchanged outgoing page, followed by an ordinary navigation with no transition at all.

To limit cross-document transitions to specific navigations, keep the `@view-transition` opt-in and skip the transition in `pageswap` when the destination isn't one you want to animate:

```javascript
window.addEventListener("pageswap", (event) => {
  const viewTransition = event.viewTransition;
  if (!viewTransition) return;


  const destination = event.activation?.entry?.url;
  const isProductNavigation =
    destination && new URL(destination).pathname.startsWith("/products/");


  if (!isProductNavigation || userPrefersReducedMotion()) {
    viewTransition.skipTransition();
  }
});
```

**Caution:**

Don't add a `.catch()` on `viewTransition.finished` as a navigation fallback. `finished` fulfills even when the transition is skipped, so the fallback is dead code. Only `viewTransition.ready` rejects on a skipped transition.

### CSS for view transitions

```css
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}


@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.01s;
  }
}


::view-transition-old(product-image),
::view-transition-new(product-image) {
  animation-duration: 0.25s;
}


.product-image {
  view-transition-name: product-image;
}
```

### Common issue: view transitions blocking interactions

Problem: The transition blocks the user from interacting until it completes. Solution: Implement cancellation on interaction, as shown in the [Cancel transitions on user interaction](#cancel-transitions-on-user-interaction) section.

### Common issue: transitions on every navigation

Problem: Overuse causes motion sickness and slows navigation. Solution: Use view transitions selectively, only for product cards and galleries.

### Common issue: ignoring the reduced motion preference

Problem: Transitions show for users who prefer less motion. Solution: Check the `prefers-reduced-motion` media query.

***

## Testing

* Enable view transitions and profile navigation interactions in the [Chrome DevTools Performance panel](https://developer.chrome.com/docs/devtools/performance).
* Interact during a transition (click, tap, or keyboard) and measure INP before and after adding the cancellation implementation.
* Test navigation from a collection page to a product page during a transition.
* Test multiple quick clicks during a transition.
* Test keyboard navigation and touch interactions on mobile during a transition.
* Test with a slow network using [network throttling](https://developer.chrome.com/docs/devtools/network#throttle).
* Compare with and without view transitions enabled.
* Log transitions for debugging:

```javascript
window.addEventListener("pageswap", (event) => {
  if (event.viewTransition) {
    console.log("View transition active");
    event.viewTransition.finished.then(() => {
      console.log("Transition completed");
    });
  }
});
```

***

## References

* [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) on MDN.
* [`@view-transition`](https://developer.mozilla.org/en-US/docs/Web/CSS/@view-transition) on MDN.
* [`Window: pageswap` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pageswap_event) on MDN.
* [`Window: pagereveal` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagereveal_event) on MDN.
* [`ViewTransition.skipTransition()`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/skipTransition) on MDN.
* [View Transitions examples](https://mdn.github.io/dom-examples/view-transitions/mpa/) (live demo).
* [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) (accessibility consideration).
* [Understanding INP](https://shopify.dev/docs/storefronts/themes/best-practices/performance/understanding-inp)
* [Optimize event handlers for responsiveness](https://shopify.dev/docs/storefronts/themes/best-practices/performance/debounce-throttle-event-handlers)

***
