View Transitions API in a Shopify theme
How I wired up AJAX page transitions in a Dawn-based Shopify theme with about 30 lines of vanilla JS and zero build tools.
Shopify themes are a constrained environment: Liquid templates, no bundler, no framework, and a build system you don't control. That constraint makes the View Transitions API a good fit — it's a browser API that needs almost no JavaScript to produce a meaningful improvement.
What it does
document.startViewTransition() takes a callback that updates the DOM. The
browser captures before/after states and animates between them. For a storefront
that means navigation feels instant instead of causing a full white-flash reload.
The implementation
async function navigate(url) {
const response = await fetch(url);
const html = await response.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
document.startViewTransition(() => {
document.querySelector('main').innerHTML =
doc.querySelector('main').innerHTML;
window.history.pushState({}, '', url);
});
}
document.addEventListener('click', (e) => {
const link = e.target.closest('a[href]');
if (!link) return;
const url = new URL(link.href);
if (url.origin !== location.origin) return;
e.preventDefault();
navigate(url.href);
});Intercept internal link clicks, fetch the target page, swap <main>, push
history. The transition handles the rest.
What to watch out for
Shopify sections that reinitialise on load. Any JS that runs in
DOMContentLoaded won't re-run after a client-side swap. Move section init
into a function you can call after the transition callback resolves.
Cart state. The mini-cart lives outside <main>. Fetch and update it
separately, or you'll have a cart counter showing stale data after navigation.
prefers-reduced-motion. Wrap the transition in a check:
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReduced && document.startViewTransition) {
document.startViewTransition(swap);
} else {
swap();
}Browser support
Chromium only as of early 2026, with Firefox behind a flag. Good enough for a storefront where the majority of traffic is Chrome on Android — and the fallback (a normal navigation) is seamless because the code degrades cleanly.
Thirty lines of JS. No build step. Noticeably better than a full page reload.