Introduction
A compelling product carousel immediately grabs attention and highlights key products, boosting conversions in your Shopify store. By leveraging Shopify 2.0’s section blocks, you can create a dynamic, merchant-friendly carousel that allows store owners to easily add, reorder, and customize slides directly from the theme editor, without needing to write code. This guide will teach you how to build a carousel that enhances the user experience and improves sales.

Understanding Section Blocks for Carousels
With block-based carousels you get: Dynamic Management where merchants can add or remove slides easily in the theme editor; Custom Settings per Slide such as unique images, headlines, and buttons for each block; easy Reordering with drag-and-drop functionality to change slide order without coding; and Preset Configurations to provide default slides for an out-of-the-box ready carousel.
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
presetsso 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_blockslimits slides to 8.blocksarray defines each slide’s settings: picking a product, optional override image, and heading.presetsseeds 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` to handle the carousel’s interactive behavior. Use JavaScript event listeners to detect when the page is loaded, then select all elements with the `data-carousel` attribute to initialize the carousel functionality. By using JavaScript, you can create dynamic transitions, button-controlled navigation, and improve the user experience. Don’t forget to include the stylesheet in your layout under. Consider exploring JavaScript frameworks for advanced carousel features.

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-autoplayanddata-autoplay-intervalattributes. - 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

Creating a dynamic product carousel using Shopify 2.0 section blocks integrates Liquid, CSS, and JavaScript for a flexible, user-friendly component. By defining block-based slides, crafting responsive styles, and wiring up interactive behavior, you empower merchants with a drag-and-drop carousel they can manage without code. Layer in optional autoplay settings and thoughtful defaults via JSON templates for a polished, easily maintainable solution.























































































































































































































































































































































































































































































































































































































































































