Free Testimonials Carousel Section for ShopifyNo app. Copy, paste, done.
- Online Store 2.0
- No app
- Theme Check: 0 errors
- MIT license
The Stackframes testimonials carousel is a free Shopify section that shows customer quotes as swipeable cards, with optional star ratings, names and photos. It is built on native CSS scroll-snap, so it swipes smoothly on phones, scrolls with the keyboard and loads no slider library. The previous and next buttons move one card at a time. It never autoplays.
Live preview with sample content. Once installed, fonts follow your theme and colours are editable in the theme editor.
{%- if section.blocks.size > 0 or request.design_mode -%}
<sf-testimonials
class="sf-testi"
style="--sf-testi-per-view: {{ section.settings.per_view }}; --sf-testi-text: {{ section.settings.text_color }}; --sf-testi-card: {{ section.settings.card_color }}; --sf-testi-star: {{ section.settings.star_color }}; --sf-testi-bg: {{ section.settings.background_color }}; --sf-testi-pt: {{ section.settings.padding_top }}px; --sf-testi-pb: {{ section.settings.padding_bottom }}px;"
>
<div class="sf-testi__inner">
<div class="sf-testi__header">
{%- if section.settings.heading != blank -%}
<h2 class="sf-testi__heading">{{ section.settings.heading | escape }}</h2>
{%- endif -%}
{%- if section.blocks.size > 1 -%}
<div class="sf-testi__nav">
<button type="button" class="sf-testi__arrow sf-testi__arrow--prev" aria-label="Previous testimonials" aria-controls="SfTesti-{{ section.id }}" disabled>
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20"><path d="M15 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button type="button" class="sf-testi__arrow sf-testi__arrow--next" aria-label="Next testimonials" aria-controls="SfTesti-{{ section.id }}">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
{%- endif -%}
</div>
{%- if section.blocks.size > 0 -%}
<ul class="sf-testi__track" id="SfTesti-{{ section.id }}" role="list" aria-label="{{ section.settings.heading | default: 'Testimonials' | escape }}" tabindex="0">
{%- for block in section.blocks -%}
<li class="sf-testi__card" {{ block.shopify_attributes }}>
<figure class="sf-testi__figure">
{%- if block.settings.rating > 0 -%}
<p class="sf-testi__stars">
<span aria-hidden="true">
{%- for i in (1..5) -%}
<svg class="sf-testi__star{% if i > block.settings.rating %} sf-testi__star--off{% endif %}" focusable="false" viewBox="0 0 24 24" width="18" height="18"><path d="M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z" fill="currentColor"/></svg>
{%- endfor -%}
</span>
<span class="sf-testi__sr">Rated {{ block.settings.rating }} out of 5</span>
</p>
{%- endif -%}
<blockquote class="sf-testi__quote"><p>{{ block.settings.quote | escape | newline_to_br }}</p></blockquote>
<figcaption class="sf-testi__author">
{%- if block.settings.avatar != blank -%}
{{ block.settings.avatar | image_url: width: 96 | image_tag: width: 48, height: 48, loading: 'lazy', alt: '', class: 'sf-testi__avatar' }}
{%- endif -%}
<span class="sf-testi__who">
<span class="sf-testi__name">{{ block.settings.author | escape }}</span>
{%- if block.settings.detail != blank -%}
<span class="sf-testi__detail">{{ block.settings.detail | escape }}</span>
{%- endif -%}
</span>
</figcaption>
</figure>
</li>
{%- endfor -%}
</ul>
{%- else -%}
<p class="sf-testi__empty">Add testimonial blocks to show what customers say.</p>
{%- endif -%}
</div>
</sf-testimonials>
{%- endif -%}
{% stylesheet %}
.sf-testi { display: block; background: var(--sf-testi-bg); color: var(--sf-testi-text); padding: var(--sf-testi-pt) 20px var(--sf-testi-pb); }
.sf-testi__inner { max-width: 1200px; margin: 0 auto; }
.sf-testi__header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
.sf-testi__heading { margin: 0; font-size: clamp(24px, 19px + 1.2vw, 36px); line-height: 1.15; }
.sf-testi__nav { display: flex; flex: none; gap: 8px; }
.sf-testi__arrow { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 1px solid color-mix(in srgb, currentColor 25%, transparent); border-radius: 999px; background: transparent; color: inherit; cursor: pointer; }
.sf-testi__arrow:hover:not(:disabled) { background: color-mix(in srgb, currentColor 8%, transparent); }
.sf-testi__arrow:disabled { opacity: 0.35; cursor: default; }
.sf-testi__arrow:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.sf-testi__track { display: grid; grid-auto-flow: column; grid-auto-columns: calc((100% - (var(--sf-testi-per-view) - 1) * 20px) / var(--sf-testi-per-view)); gap: 20px; margin: 0; padding: 0 0 8px; list-style: none; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: thin; }
.sf-testi__track:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }
.sf-testi__card { scroll-snap-align: start; }
.sf-testi__figure { display: flex; flex-direction: column; height: 100%; margin: 0; padding: 24px; border-radius: 12px; background: var(--sf-testi-card); }
.sf-testi__stars { display: flex; margin: 0 0 12px; color: var(--sf-testi-star); }
.sf-testi__star--off { opacity: 0.25; }
.sf-testi__quote { flex: 1; margin: 0; font-size: 1.0625em; line-height: 1.55; }
.sf-testi__quote p { margin: 0; }
.sf-testi__author { display: flex; align-items: center; gap: 12px; margin-top: 20px; }
.sf-testi__avatar { flex: none; width: 48px; height: 48px; border-radius: 999px; object-fit: cover; }
.sf-testi__who { display: flex; flex-direction: column; }
.sf-testi__name { font-weight: 700; }
.sf-testi__detail { font-size: 0.875em; opacity: 0.75; }
.sf-testi__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.sf-testi__empty { margin: 0; padding: 24px; border: 1px dashed currentColor; border-radius: 8px; text-align: center; opacity: 0.7; }
@media (max-width: 749px) {
.sf-testi__track { grid-auto-columns: 85%; }
}
{% endstylesheet %}
{% javascript %}
if (!customElements.get('sf-testimonials')) {
class SfTestimonials extends HTMLElement {
get track() {
return this.querySelector('.sf-testi__track');
}
connectedCallback() {
this.onClick = (event) => {
const arrow = event.target.closest('.sf-testi__arrow');
if (!arrow || arrow.disabled) return;
this.step(arrow.classList.contains('sf-testi__arrow--prev') ? -1 : 1);
};
this.onScroll = (event) => {
if (event.target.classList && event.target.classList.contains('sf-testi__track')) this.updateArrows();
};
this.onSelect = (event) => {
if (!this.contains(event.target)) return;
const card = event.target.closest('.sf-testi__card');
if (card) card.scrollIntoView({ behavior: this.behavior(), block: 'nearest', inline: 'start' });
};
this.addEventListener('click', this.onClick);
this.addEventListener('scroll', this.onScroll, { capture: true, passive: true });
document.addEventListener('shopify:block:select', this.onSelect);
this.resizeObserver = typeof ResizeObserver === 'function' ? new ResizeObserver(() => this.updateArrows()) : null;
if (this.resizeObserver && this.track) this.resizeObserver.observe(this.track);
this.updateArrows();
}
disconnectedCallback() {
this.removeEventListener('click', this.onClick);
this.removeEventListener('scroll', this.onScroll, { capture: true });
document.removeEventListener('shopify:block:select', this.onSelect);
if (this.resizeObserver) this.resizeObserver.disconnect();
this.resizeObserver = null;
}
behavior() {
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
}
step(direction) {
const track = this.track;
const card = track && track.querySelector('.sf-testi__card');
if (!card) return;
const rtl = getComputedStyle(this).direction === 'rtl';
const gap = parseFloat(getComputedStyle(track).columnGap) || 0;
track.scrollBy({ left: direction * (rtl ? -1 : 1) * (card.getBoundingClientRect().width + gap), behavior: this.behavior() });
}
updateArrows() {
const track = this.track;
if (!track) return;
const max = track.scrollWidth - track.clientWidth - 1;
const pos = Math.abs(track.scrollLeft);
const prev = this.querySelector('.sf-testi__arrow--prev');
const next = this.querySelector('.sf-testi__arrow--next');
const prevOff = pos <= 0;
const nextOff = pos >= max;
const active = document.activeElement;
if (prev && !prevOff) prev.disabled = false;
if (next && !nextOff) next.disabled = false;
if (prev && prevOff && active === prev) (next && !nextOff ? next : track).focus({ preventScroll: true });
if (next && nextOff && active === next) (prev && !prevOff ? prev : track).focus({ preventScroll: true });
if (prev && prevOff) prev.disabled = true;
if (next && nextOff) next.disabled = true;
}
}
customElements.define('sf-testimonials', SfTestimonials);
}
{% endjavascript %}
{% schema %}
{
"name": "SF testimonials",
"tag": "section",
"class": "sf-testi-section",
"disabled_on": { "groups": ["header", "footer"] },
"max_blocks": 16,
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "What customers say" },
{ "type": "range", "id": "per_view", "label": "Cards visible on desktop", "min": 1, "max": 3, "step": 1, "default": 3 },
{ "type": "header", "content": "Colors" },
{ "type": "color", "id": "text_color", "label": "Text", "default": "#121212" },
{ "type": "color", "id": "card_color", "label": "Card", "default": "#f5f5f4" },
{ "type": "color", "id": "star_color", "label": "Stars", "default": "#b45309" },
{ "type": "color", "id": "background_color", "label": "Background", "default": "#ffffff" },
{ "type": "header", "content": "Spacing" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 48 },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 48 }
],
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{ "type": "textarea", "id": "quote", "label": "Quote", "info": "Replace the sample quotes with real reviews from your customers.", "default": "Add a customer quote here." },
{ "type": "text", "id": "author", "label": "Name", "default": "Customer name" },
{ "type": "text", "id": "detail", "label": "Detail", "info": "For example a city or the product they bought." },
{ "type": "range", "id": "rating", "label": "Star rating", "info": "Set to 0 to hide the stars.", "min": 0, "max": 5, "step": 1, "default": 5 },
{ "type": "image_picker", "id": "avatar", "label": "Photo" }
]
}
],
"presets": [
{
"name": "SF testimonials",
"blocks": [
{ "type": "testimonial", "settings": { "quote": "Add a real customer quote here.", "author": "Customer name", "detail": "Replace with a real review", "rating": 5 } },
{ "type": "testimonial", "settings": { "quote": "Paste another review from a happy customer.", "author": "Customer name", "rating": 5 } },
{ "type": "testimonial", "settings": { "quote": "Short, specific quotes work best.", "author": "Customer name", "rating": 5 } }
]
}
]
}
{% endschema %}
- Native scroll-snap — smooth swiping, no slider library
- Star ratings with screen-reader text
- 1–3 cards on desktop, peeking cards on phones
- No autoplay; arrows disable at the ends
- Scrolls to the testimonial you're editing in the theme editor
Add the Testimonials Carousel to your theme
About two minutes. No app, no monthly fee.
Prefer a walkthrough? See the full installation guide.
- STEP 01
Duplicate your theme
In Shopify admin go to Online Store → Themes. Next to your current theme click ⋯ → Duplicate. Make every change on the copy.
- STEP 02
Create the section file
On the copy, click ⋯ → Edit code. In the sections folder click “Add a new section”, choose Liquid, name it sf-testimonials-carousel and create it. Delete the starter code, paste ours, then click Save.
- STEP 03
Add it to a page
Click Customize, open the template you want, click “Add section” and choose “SF testimonials”. Edit the text, blocks and colours in the sidebar, then Save.
- STEP 04
Check it, then publish
Preview on mobile and desktop. When you are happy, publish the duplicate theme.
What you can change in the theme editor
| Setting | Type | Default |
|---|---|---|
| Heading | Text | What customers say |
| Cards visible on desktop | Slider | 3 |
| Text | Colour | #121212 |
| Card | Colour | #f5f5f4 |
| Stars | Colour | #b45309 |
| Background | Colour | #ffffff |
| Top padding | Slider | 48px |
| Bottom padding | Slider | 48px |
| Setting | Type | Default |
|---|---|---|
| QuoteReplace the sample quotes with real reviews from your customers. | Text (multi-line) | Add a customer quote here. |
| Name | Text | Customer name |
| DetailFor example a city or the product they bought. | Text | — |
| Star ratingSet to 0 to hide the stars. | Slider | 5 |
| Photo | Image | — |
This section is one piece of a custom theme.
We design and build complete Shopify themes around your brand.
Common questions
Does this pull reviews from a review app?
No. You type each testimonial into the theme editor. That keeps it fast and app-free. If you want it synced with a review app, we can build that for you.
Can I use real customer photos?
Yes. Add a photo to each testimonial block. Only publish real quotes and photos you have permission to use, and replace the sample testimonials before going live.
Why doesn't it autoplay?
Autoplaying carousels are hard to read and are an accessibility problem. Shoppers swipe or use the arrows at their own pace.
More free sections
Logo Marquee
Free scrolling logo bar for Shopify — "as seen in" or partner logos. Pure CSS animation, pause button, reduced-motion friendly. Copy, paste, done.
Trust Badges
Free trust badge section for Shopify: shipping, returns, secure checkout and support icons. Lightweight inline SVG, no JavaScript, no app.
Before/After Slider
Free before and after image slider for Shopify. Drag the handle or use the keyboard to compare two photos. Accessible, lightweight Liquid, no app.
All free Shopify sections
Get a Shopify theme built around your brand
Custom sections, a full theme build, or changes to the theme you already have. Tell us what your store needs and we'll scope it with you.
Not affiliated with or endorsed by Shopify Inc. Shopify is a trademark of Shopify Inc.