How to Add a Product Review Carousel to Shopify Without an App
Shopify's free themes ship with a product reviews section that renders a plain list of reviews under the product description. It works, but it buries your best social proof below the fold and gives shoppers no reason to scroll back up. A review carousel fixes that: it keeps rotating proof of quality in front of the buyer while they're deciding. You can build one with Liquid, CSS, and a small amount of JavaScript — no monthly app fee, no third-party script slowing down your store.
Why Build a Review Carousel Without an App
Review apps like Judge.me, Loox, and Okendo earn their fee when you need photo reviews, review request emails, Google rich snippet markup, or moderation dashboards. If your store already collects reviews through Shopify's built-in product reviews or a lightweight app, paying $10–$30 per month just to rotate them is hard to justify.
Building it yourself gives you three things:
- Control over markup and speed. No external script, no iframe, no third-party font request. The carousel loads with your theme.
- No lock-in. Review data stays in your theme or metafields. You can restyle it, move it, or delete it without exporting anything.
- Exact placement. You decide whether it sits above the Add to cart button, below the description, or in a separate section you can toggle per template.
The trade-off is real: you're responsible for maintenance, and you won't get automatic review collection or photo galleries. If you need those, a free tier of Judge.me or the built-in Shopify review system plus this carousel is a reasonable middle ground. Check current pricing before committing to any paid tier.
What You Need Before You Start
Before touching code, confirm these:
- A Shopify plan that allows theme code editing. All current plans do; you just need admin access.
- A duplicate of your live theme. In Online Store → Themes, click the three dots next to your live theme and choose Duplicate. Never edit the live theme directly.
- Reviews that exist somewhere. Either Shopify's native product reviews, a review app with a public widget or API, or a metafield you've populated.
- Basic comfort with Liquid and CSS. You don't need to be a developer, but you should know what a
{% for %}loop does.
If your reviews live inside a review app that only renders its own widget, you have two options: keep the widget and skip this tutorial, or export reviews to metafields. Most apps let you export reviews as CSV, which you can import into a product metafield using a bulk editor.
Step 1: Create the Review Carousel Section in Liquid
In your duplicated theme, go to Edit code and open the sections folder. Click Add a new section, name it review-carousel, and paste this:
<section class="review-carousel" data-autoplay="true" data-interval="5000">
<h2 class="review-carousel__heading">{{ section.settings.heading }}</h2>
<div class="review-carousel__track" id="reviewTrack-{{ section.id }}">
{% for block in section.blocks %}
<article class="review-card" {{ block.shopify_attributes }}>
<div class="review-card__stars" aria-label="{{ block.settings.rating }} out of 5 stars">
{% for i in (1..5) %}
<span class="{% if i <= block.settings.rating %}is-filled{% endif %}">★</span>
{% endfor %}
</div>
<p class="review-card__text">"{{ block.settings.review_text }}"</p>
<p class="review-card__author">— {{ block.settings.author }}</p>
</article>
{% endfor %}
</div>
<div class="review-carousel__dots" role="tablist"></div>
</section>
{% schema %}
{
"name": "Review Carousel",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "What customers say" }
],
"blocks": [
{
"type": "review",
"name": "Review",
"settings": [
{ "type": "range", "id": "rating", "min": 1, "max": 5, "step": 1, "label": "Star rating", "default": 5 },
{ "type": "textarea", "id": "review_text", "label": "Review text" },
{ "type": "text", "id": "author", "label": "Author name" }
]
}
],
"presets": [{ "name": "Review Carousel" }]
}
{% endschema %}
This creates a section you can add to any product template through the theme editor. Each review is a block, so you can add, reorder, and delete reviews without touching code. The data-interval attribute controls rotation speed in milliseconds.
Step 2: Add the CSS for the Rotating Carousel
Open assets/base.css or your theme's main stylesheet and append:
.review-carousel { max-width: 720px; margin: 2rem auto; overflow: hidden; }
.review-carousel__heading { text-align: center; margin-bottom: 1rem; }
.review-carousel__track {
display: flex;
transition: transform 0.5s ease;
will-change: transform;
}
.review-card {
flex: 0 0 100%;
padding: 1.5rem;
text-align: center;
box-sizing: border-box;
}
.review-card__stars { color: #d4d4d4; font-size: 1.25rem; letter-spacing: 2px; }
.review-card__stars .is-filled { color: #f5a623; }
.review-card__text { font-size: 1.05rem; line-height: 1.6; margin: 0.75rem 0; }
.review-card__author { font-size: 0.9rem; opacity: 0.75; }
.review-carousel__dots { display: flex; justify-content: center; gap: 8px; margin-top: 1rem; }
.review-carousel__dots button {
width: 10px; height: 10px; border-radius: 50%;
border: none; background: #ccc; cursor: pointer; padding: 0;
}
.review-carousel__dots button.is-active { background: #333; }
The flex: 0 0 100% on each card makes one review fill the viewport at a time. If you'd rather show two or three at once on desktop, change that to flex: 0 0 50% or 33.333% and adjust the JavaScript step count accordingly.
Step 3: Add the JavaScript for Auto-Rotation
Create assets/review-carousel.js and add:
document.querySelectorAll('.review-carousel').forEach((carousel) => {
const track = carousel.querySelector('.review-carousel__track');
const cards = track.children;
const dotsWrap = carousel.querySelector('.review-carousel__dots');
let index = 0;
const interval = parseInt(carousel.dataset.interval, 10) || 5000;
let timer;
for (let i = 0; i < cards.length; i++) {
const dot = document.createElement('button');
dot.setAttribute('aria-label', `Go to review ${i + 1}`);
dot.addEventListener('click', () => { goTo(i); reset(); });
dotsWrap.appendChild(dot);
}
const dots = dotsWrap.children;
function goTo(i) {
index = (i + cards.length) % cards.length;
track.style.transform = `translateX(-${index * 100}%)`;
[...dots].forEach((d, n) => d.classList.toggle('is-active', n === index));
}
function reset() { clearInterval(timer); start(); }
function start() {
if (carousel.dataset.autoplay !== 'true' || cards.length < 2) return;
timer = setInterval(() => goTo(index + 1), interval);
}
carousel.addEventListener('mouseenter', () => clearInterval(timer));
carousel.addEventListener('mouseleave', start);
goTo(0);
start();
});
Then load it in layout/theme.liquid just before the closing </body> tag:
<script src="{{ 'review-carousel.js' | asset_url }}" defer></script>
The script pauses on hover, respects reduced-motion users by simply not autoplaying if you set data-autoplay="false", and works with any number of blocks.
Step 4: Connect the Carousel to Your Product Reviews
Hardcoding reviews into blocks works for a handful of products, but it doesn't scale. Two better approaches:
Metafields. Create a product metafield of type "JSON" called reviews.carousel. Store an array of review objects. Replace the {% for block in section.blocks %} loop with a loop over product.metafields.reviews.carousel.value. This lets you update reviews without editing the section.
Review app export. If you use Judge.me, Loox, or similar, export reviews as CSV, then map the columns to a JSON metafield. Most apps document their export format in their help center.
Native Shopify reviews. Shopify's built-in product reviews store data on the product, but the public API is limited. The simplest path is to render the native reviews block and let the carousel wrap it — but you'll lose the per-card structure. For most stores, metafields are the cleaner route.
| Approach | Setup effort | Scales to many products | Needs app |
|---|---|---|---|
| Section blocks | Low | No | No |
| Product metafields | Medium | Yes | No |
| Review app export | Medium | Yes | Yes |
| Native Shopify reviews | Low | Partial | No |
Step 5: Test and Style the Carousel on Mobile
Preview the section in the theme editor, then switch to mobile view. Check these:
- Card height consistency. Long reviews will stretch the track. Set a
min-heighton.review-cardor clamp text with-webkit-line-clamp. - Touch targets. Dots should be at least 24px tall on mobile. Increase padding, not just size.
- Swipe support. The script above doesn't handle touch swipes. Add a simple touchstart/touchend listener if you want it, or accept dots-only navigation, which is fine for most stores.
- Font size. Bump
.review-card__textto at least 16px on mobile to avoid iOS zoom on tap.
Test in Chrome DevTools device mode and on a real phone before publishing.
Common Issues and How to Fix Them
The carousel doesn't rotate. Check that data-autoplay="true" is on the section, that the JS file is actually loading (look for a 404 in the console), and that there are at least two review blocks.
All cards show at once. Your theme's CSS is likely overriding .review-carousel__track with flex-wrap: wrap. Add flex-wrap: nowrap explicitly.
The track jumps instead of sliding. The transition is on the wrong element, or a parent has overflow: hidden with a fixed height clipping the animation.
Reviews appear on every product. You added the section to the product template rather than a specific template. Duplicate your product template, assign it to the products you want, and add the section there.
Theme update wipes the section. Store the Liquid in a snippet and reference it, or keep a copy of the section file outside Shopify. Theme updates overwrite custom sections.
Conclusion
A review carousel built with Liquid, CSS, and a small script gives you social proof that loads fast, sits exactly where you want it, and costs nothing per month. Start with section blocks for your hero products, move to metafields once you have more than a dozen reviews, and only add a paid review app when you genuinely need photo reviews or automated collection. The whole build takes about an hour in a duplicated theme.
FAQ
Will this work with any Shopify theme?
It works with Online Store 2.0 themes (Dawn, Refresh, Craft, and most paid themes released since 2021). Older themes that don't support sections in the theme editor will need the code placed directly in product.liquid.
Does the carousel hurt page speed? No, if you keep the CSS and JS in your theme's asset pipeline. The script is under 1KB and there are no external requests. Avoid adding a review app widget on top of it.
Can I show star ratings in Google search results?
Only if you add valid Product and AggregateRating structured data to the page. The carousel itself doesn't generate rich snippets. Use Shopify's built-in structured data or add JSON-LD manually, and test with Google's Rich Results Test before relying on it.