How to Add a Product Review Carousel to Shopify Without an App

2026-09-24 · product reviews

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.

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:

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:

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.

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.

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.

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

Preview the section in the theme editor, then switch to mobile view. Check these:

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.