Introduction
A well-designed product carousel can instantly capture shopper attention, showcase featured items, and boost conversions on your Shopify store. With Shopify 2.0’s flexible section blocks, you can build a truly dynamic, merchant-friendly carousel that lets store owners add, reorder, and customize slides right from the theme editor—no code required. In this guide, you’ll learn how to scaffold a carousel section, define blocks for each slide, style with CSS for smooth transitions, and add JavaScript for swipe and autoplay behavior. By the end, you’ll have a reusable, responsive product carousel that adapts to any storefront and drives engagement.

Understanding Section Blocks for Carousels
What Are Section Blocks?
Shopify 2.0 enables sections on every page and introduces blocks—repeatable, draggable items within a section. Blocks are ideal for carousels because each slide can be a block with its own settings (image, heading, link).
Benefits of a Block-Based Carousel
- Dynamic Management: Merchants can add or remove slides in the theme editor.
- Custom Settings per Slide: Unique images, headlines, buttons per block.
- Reorder Easily: Drag-and-drop to change slide order without touching code.
- Preset Configurations: Provide default slides via
presets
so the carousel looks ready out of the box.
Prerequisites and Setup
- Shopify 2.0 Theme: Ensure your theme supports JSON templates and sections.
- Shopify CLI: For local development (
shopify theme serve
). - Code Editor: VS Code or similar, with Liquid support.
- Basic JavaScript & CSS: Familiarity with event listeners and transforms.
Initialize your theme locally:
bashCopyEditshopify login --store your-dev-store.myshopify.com
shopify theme init my-carousel-theme
cd my-carousel-theme
shopify theme serve
Step 1: Scaffolding the Carousel Section
Create a new file at sections/product-carousel.liquid
. Begin with the boilerplate:

liquidCopyEdit{% schema %}
{
"name": "Product Carousel",
"max_blocks": 8,
"blocks": [
{
"type": "slide",
"name": "Slide",
"settings": [
{
"type": "product",
"id": "product",
"label": "Select Product"
},
{
"type": "image_picker",
"id": "image",
"label": "Custom Slide Image (optional)"
},
{
"type": "text",
"id": "heading",
"label": "Slide Heading",
"default": ""
}
]
}
],
"presets": [
{
"name": "Default Carousel",
"category": "Featured"
}
]
}
{% endschema %}
max_blocks
limits slides to 8.blocks
array defines each slide’s settings: picking a product, optional override image, and heading.presets
seeds a default empty carousel in new themes.
Step 2: Writing the Carousel Markup
Above the schema, add markup that loops through each block:
liquidCopyEdit<div class="carousel" data-carousel>
<div class="carousel__track">
{% for block in section.blocks %}
{% assign prod = all_products[block.settings.product] %}
<div class="carousel__slide" data-slide-index="{{ forloop.index0 }}">
<a href="{{ prod.url }}" class="carousel__link">
<img
src="{{ block.settings.image | img_url: '800x' | default: prod.featured_image | img_url: '800x' }}"
alt="{{ block.settings.heading | default: prod.title }}"
class="carousel__image"
>
</a>
<div class="carousel__caption">
<h3>{{ block.settings.heading | default: prod.title }}</h3>
<p>{{ prod.price | money }}</p>
</div>
</div>
{% endfor %}
</div>
<button class="carousel__prev" aria-label="Previous slide">‹</button>
<button class="carousel__next" aria-label="Next slide">›</button>
</div>
.carousel__track
: Flex container for slides.data-
attributes: Used to initialize JS behavior.- Image source: Uses custom image if set, else product’s featured image.
- Caption: Heading and price pulled dynamically.
Step 3: Styling with CSS
Add styles in assets/product-carousel.css
:
cssCopyEdit.carousel {
position: relative;
overflow: hidden;
}
.carousel__track {
display: flex;
transition: transform 0.5s ease;
}
.carousel__slide {
min-width: 100%;
box-sizing: border-box;
position: relative;
}
.carousel__image {
width: 100%;
display: block;
}
.carousel__caption {
position: absolute;
bottom: 1rem;
left: 1rem;
background: rgba(0,0,0,0.5);
color: #fff;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.carousel__prev,
.carousel__next {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.5);
border: none;
color: #fff;
font-size: 2rem;
padding: 0 0.5rem;
cursor: pointer;
}
.carousel__prev { left: 0.5rem; }
.carousel__next { right: 0.5rem; }
Include the stylesheet in your layout:
liquidCopyEdit{{ 'product-carousel.css' | asset_url | stylesheet_tag }}
Step 4: Adding JavaScript for Interactivity
Create assets/product-carousel.js
:

jsCopyEditdocument.addEventListener('DOMContentLoaded', () => {
const carousels = document.querySelectorAll('[data-carousel]');
carousels.forEach(initCarousel);
});
function initCarousel(carousel) {
const track = carousel.querySelector('.carousel__track');
const slides = Array.from(track.children);
const prevButton = carousel.querySelector('.carousel__prev');
const nextButton = carousel.querySelector('.carousel__next');
let currentIndex = 0;
function updateTrack() {
const offset = -currentIndex * carousel.clientWidth;
track.style.transform = `translateX(${offset}px)`;
}
prevButton.addEventListener('click', () => {
currentIndex = (currentIndex - 1 + slides.length) % slides.length;
updateTrack();
});
nextButton.addEventListener('click', () => {
currentIndex = (currentIndex + 1) % slides.length;
updateTrack();
});
// Optional autoplay
let autoplay = carousel.dataset.autoplay === 'true';
if (autoplay) {
setInterval(() => {
nextButton.click();
}, parseInt(carousel.dataset.autoplayInterval, 10) || 5000);
}
// Resize handler
window.addEventListener('resize', updateTrack);
}
Include it in your layout under <body>
:
liquidCopyEdit{{ 'product-carousel.js' | asset_url | script_tag }}
updateTrack()
shifts slides via CSS transforms.- Autoplay support via
data-autoplay
anddata-autoplay-interval
attributes. - Responsive: Recalculates positions on
resize
.
Step 5: Exposing Autoplay Settings (Optional)
Allow merchants to toggle autoplay and interval via section settings. In your schema’s root:
jsonCopyEdit"settings": [
{
"type": "checkbox",
"id": "autoplay",
"label": "Enable Autoplay",
"default": true
},
{
"type": "number",
"id": "autoplay_interval",
"label": "Autoplay Interval (ms)",
"min": 2000,
"max": 10000,
"step": 500,
"default": 5000
}
]
In the markup wrapper:
liquidCopyEdit<div
class="carousel"
data-carousel
data-autoplay="{{ section.settings.autoplay }}"
data-autoplay-interval="{{ section.settings.autoplay_interval }}"
>
Merchants can now control autoplay behavior directly in the theme editor.
Conclusion

Building a dynamic product carousel using Shopify 2.0 section blocks combines the power of Liquid, CSS, and JavaScript to create a flexible, user-friendly component. By defining block-based slides, crafting responsive styles, and wiring up interactive behavior, you give merchants a drag-and-drop carousel they can manage without code. Layer in optional autoplay settings and thoughtful defaults via presets, and you’ll deliver a polished, conversion-driving carousel that enhances any storefront. Ready to bring your products into motion? Start customizing and watch engagement soar.