View transitions are a game-changing feature that enable seamless animations during page changes within a website. This creates a smooth and intuitive user experience that was previously unattainable.
Although view transitions are relatively new, they already have 79% support and don’t require polyfills. In unsupported browsers, the user simply won’t see the animation, maintaining functionality without compromise.
For many frameworks, such as Astro or Next, developers and communities have already introduced solutions to implement view transitions. However, they’re still rare in Shopify themes, which is why we’re using a Shopify store as the basis for this guide.
Info
This tutorial applies to multi-page applications (MPAs) and isn’t limited to Shopify. A Shopify theme is used as an example.
See the result:
- Preview link - https://oleksii-s.myshopify.com/.
- Password - Test.
If you are only interested in the code, check out the sections: basic implementation, product card to PDP, shuffling product cards on PLP, GitHub.
Preparing the demo store
Before diving into the implementation, let’s prepare a demo store to better understand the examples. If you already have a development store set up, feel free to skip this section.
So for this article used:
- Dawn theme v15.2.0 (GitHub).
- The products described in this article are from Shopify (Download CSV).
- For simplicity, I didn't use any builders and just used the Shopify CLI for deployment.
- I also disabled animation from the theme ("Theme settings" → "Animations" → "Reveal sections on scroll" → "Off").
Adding view transitions to changing page
To demonstrate, we’ll add view transitions for a smooth animation from a product card to the product detail page (PDP). First, let’s implement global changes to enable view transitions across the site.
Global Setup
0123
...
<head>
<meta name="view-transition" content="same-origin" />
...
0123456789
...
@view-transition {
navigation: auto;
}
@media (prefers-reduced-motion) {
::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
animation: none !important
}
}
Add a <meta>
tag to inform the browser about the usage of view transitions. Additionally, use the @view-transition
CSS rule. It’s also good practice to disable animations for users who prefer reduced motion.
Implementing Product Card to PDP Transitions
Next, apply the view-transition-name
CSS property to the product card and PDP. The values for this property must match on both pages to trigger the animation.
012345678910
...
{%- when 'title' -%}
<div class="product__title" {{ block.shopify_attributes }}>
<h1 style="view-transition-name: product-title-{{- product.id -}}">{{ product.title | escape }}</h1>
<a href="{{ product.url }}" class="product__title">
<h2 class="h1">
{{ product.title | escape }}
</h2>
</a>
</div>
...
0123456789101112
...
<div class="product__media media media--transparent" style="view-transition-name: product-media-{{- media.id -}}">
{{
media.preview_image
| image_url: width: 1946
| image_tag:
class: image_class,
loading: lazy,
sizes: sizes,
widths: '246, 493, 600, 713, 823, 990, 1100, 1206, 1346, 1426, 1646, 1946'
}}
</div>
...
012345
...
<div
class="card__inner {% if settings.card_style == 'standard' %}color-{{ settings.card_color_scheme }} gradient{% endif %}{% if card_product.featured_media or settings.card_style == 'standard' %} ratio{% endif %}"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%; view-transition-name: product-media-{{- card_product.featured_media.id -}}"
>...</div>
...
01234567891011121314151617
...
<h3
class="card__heading{% if card_product.featured_media or settings.card_style == 'standard' %} h5{% endif %}"
{% if card_product.featured_media or settings.card_style == 'card' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
style="view-transition-name: product-title-{{- card_product.id -}}"
>
<a
href="{{ card_product.url }}"
id="CardLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="CardLink-{{ section_id }}-{{ card_product.id }} Badge-{{ section_id }}-{{ card_product.id }}"
>
{{ card_product.title | escape }}
</a>
</h3>
...
So as mentioned earlier, all that was added is the CSS property view-transition-name
.
- For product titles:
product-title-{{- product.id -}}
- For product images:
product-media-{{- media.id -}}
In each case, a unique value is added: product id
for title and image id
for media. So these values will always be the same for product card and PDP. Also these values should be unique on the page, i.e. for correct work there should not be multiple product cards with the same product on the page.
Here's how it looks on the store:
Important!
It's quite possible that view-transitions won't be applied at certain combinations of settings because other parts of the code (to which view-transition-name
has not been added) will be used there.
The purpose was not to add view-transitions
to every possible place in Dawn theme, but to show how they work for Shopify theme.
A few tips on using view transitions on changing pages:
- Disable other animations: Overlapping animations may overshadow view transitions.
- Avoid
scroll-behavior: smooth
: Scrolling animations can conflict with view transitions.
Trigger view transitions with JavaScript
View transitions can also be used to animate DOM changes, making them a powerful tool for single-page applications. Let’s add an animation to shuffle the product grid when filters are applied on the collection page.
01234567
...
<div class="card-information" style="view-transition-name: product-card-information-{{- card_product.id -}}">
{%- if show_vendor -%}
<span class="visually-hidden">{{ 'accessibility.vendor' | t }}</span>
<div class="caption-with-letter-spacing light">{{ card_product.vendor }}</div>
{%- endif -%}
...
</div>
01234567891011121314151617181920212223242526272829303132333435363738
...
static renderPage(searchParams, event, updateURLHash = true) {
const action = () => {
FacetFiltersForm.searchParamsPrev = searchParams;
const sections = FacetFiltersForm.getSections();
// const countContainer = document.getElementById('ProductCount');
// const countContainerDesktop = document.getElementById('ProductCountDesktop');
// const loadingSpinners = document.querySelectorAll(
// '.facets-container .loading__spinner, facet-filters-form .loading__spinner'
// );
// loadingSpinners.forEach((spinner) => spinner.classList.remove('hidden'));
// document.getElementById('ProductGridContainer').querySelector('.collection').classList.add('loading');
// if (countContainer) {
// countContainer.classList.add('loading');
// }
// if (countContainerDesktop) {
// countContainerDesktop.classList.add('loading');
// }
sections.forEach((section) => {
const url = `${window.location.pathname}?section_id=${section.section}&${searchParams}`;
const filterDataUrl = (element) => element.url === url;
FacetFiltersForm.filterData.some(filterDataUrl)
? FacetFiltersForm.renderSectionFromCache(filterDataUrl, event)
: FacetFiltersForm.renderSectionFromFetch(url, event);
});
if (updateURLHash) FacetFiltersForm.updateURLHash(searchParams);
}
if (!document.startViewTransition) {
action();
return;
}
document.startViewTransition(action);
}
...
The first tab shows the change in card-product.liquid
. Here, a view-transition-name
has been added for card info. This is the only part of the product card that did not have view-transition-name
as we already added this property for media and title in the previous step.
The second tab shows the changes in facets.js
. This is the JavaScript part of the changes. The first thing that catches the eye is the large amount of commented-out code. This is the code responsible for the loader when changing the DOM. It happens at the same time as the view transition and makes the UX less pleasant. That's why the decision was made to remove this part. For clarity, this part is shown commented out.
The next step was to put all the code of the function inside the action
. This is necessary to follow the DRY principle. We also added a browser check for view transitions support (if (!document.startViewTransition)
). If the browser doesn't support this feature - call this function instantly (action()
), otherwise, wrap the function call in the provided method (document.startViewTransition(action)
).
Here's how it looks on the store:
Conclusion
In this article, we demonstrated how to implement view transitions in a Shopify store using the Dawn theme. We covered two primary use cases:
- Page Transitions: Smooth animations between the product card and PDP.
- DOM Changes: Shuffle animations for the product grid on the collection page when filters or sorting are applied.
These examples can be scaled and adapted to any theme, making them versatile for various use cases.
All the code from the article is available on GitHub. Feel free to use.
- Preview link - https://oleksii-s.myshopify.com/.
- Password - Test.