{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Testimonials: quote cards with optional star rating, avatar (or initials) and product link. Three layouts: grid, masonry (CSS columns) or a horizontal scroll row with scroll snap and prev/next buttons (small custom element; the row still scrolls by touch, trackpad and keyboard without it). No review structured data on purpose: Google does not show self-serving reviews a store publishes about itself. Depends on: snippets/lib-button.liquid, snippets/lib-image.liquid {%- endcomment -%} {%- liquid assign s = section.settings assign heading_tag = s.heading_tag | default: 'h2' assign track_id = 'LibTestimonials-' | append: section.id -%}
{%- if s.heading != blank or s.text != blank or s.summary != blank -%}
{%- if s.heading != blank -%} <{{ heading_tag }} class="lib-testimonials__heading lib-testimonials__heading--{{ s.heading_size }}"> {{- s.heading -}} {%- endif -%} {%- if s.summary != blank -%}

{{ s.summary | escape }}

{%- endif -%} {%- if s.text != blank -%}
{{ s.text }}
{%- endif -%}
{%- endif -%}
{%- for block in section.blocks -%} {%- liquid assign b = block.settings assign rating = b.rating | plus: 0 assign initials = '' assign name_parts = b.name | strip | split: ' ' if name_parts.size > 0 assign initials = name_parts.first | slice: 0 | upcase if name_parts.size > 1 assign last_initial = name_parts.last | slice: 0 | upcase assign initials = initials | append: last_initial endif endif -%}
{%- if rating > 0 -%} {%- endif -%}
{{ b.quote }}
{%- if b.name != blank -%} {{ b.name | escape }} {%- endif -%} {%- if b.role != blank -%} {{ b.role | escape }} {%- endif -%} {%- if b.product_label != blank -%} {%- if b.product_url != blank -%} {{ b.product_label | escape }} {%- else -%} {{ b.product_label | escape }} {%- endif -%} {%- endif -%}
{%- endfor -%}
{%- if s.layout == 'scroll' and section.blocks.size > 1 -%}
{%- endif -%}
{%- if s.button_label != blank -%}
{%- render 'lib-button', label: s.button_label, url: s.button_url, style: s.button_style, new_tab: s.button_new_tab -%}
{%- endif -%}
{% stylesheet %} .lib-testimonials { background-color: var(--lib-bg); background-image: var(--lib-bg-image, none); color: var(--lib-text); padding: var(--lib-pt) 1rem var(--lib-pb); } .lib-testimonials__inner { max-width: 1200px; margin: 0 auto; } .lib-testimonials--full .lib-testimonials__inner { max-width: none; } @media (max-width: 749px) { .lib-testimonials { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-testimonials[data-show='desktop'] { display: none; } } @media (min-width: 750px) { .lib-testimonials[data-show='mobile'] { display: none; } } /* Header: alignment for desktop, then mobile. */ .lib-testimonials__header { display: flex; flex-direction: column; align-items: center; max-width: 42rem; margin: 0 auto 3rem; text-align: center; } .lib-testimonials__header[data-align='left'] { align-items: flex-start; margin-left: 0; text-align: left; } .lib-testimonials__header[data-align='right'] { align-items: flex-end; margin-right: 0; text-align: right; } @media (max-width: 749px) { .lib-testimonials__header { margin-bottom: 2rem; } .lib-testimonials__header[data-align-m='left'] { align-items: flex-start; margin-inline: 0 auto; text-align: left; } .lib-testimonials__header[data-align-m='center'] { align-items: center; margin-inline: auto; text-align: center; } .lib-testimonials__header[data-align-m='right'] { align-items: flex-end; margin-inline: auto 0; text-align: right; } } .lib-testimonials__heading { margin: 0; font-size: clamp(1.75rem, 3.5vw, 2.75rem); line-height: 1.1; letter-spacing: -0.02em; text-wrap: balance; } .lib-testimonials__heading--small { font-size: clamp(1.4rem, 2.5vw, 2rem); } .lib-testimonials__heading--large { font-size: clamp(2rem, 4.5vw, 3.5rem); } .lib-testimonials__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); } .lib-testimonials__summary { display: inline-flex; align-items: center; gap: 0.4rem; margin: 1rem 0 0; font-size: 0.95rem; font-weight: 600; } .lib-testimonials__summary-star { flex-shrink: 0; color: var(--lib-star); } .lib-testimonials__intro { margin-top: 1rem; font-size: 1.05rem; line-height: 1.6; opacity: 0.72; } .lib-testimonials__intro > * { margin: 0; } /* Layouts */ .lib-testimonials__list { --lib-gap: 1rem; } @media (min-width: 750px) { .lib-testimonials__list { --lib-gap: 1.25rem; } } .lib-testimonials[data-layout='grid'] .lib-testimonials__list { display: grid; gap: var(--lib-gap); grid-template-columns: repeat(var(--lib-cols-m), minmax(0, 1fr)); } .lib-testimonials[data-layout='masonry'] .lib-testimonials__list { column-count: var(--lib-cols-m); column-gap: var(--lib-gap); } .lib-testimonials[data-layout='masonry'] .lib-testimonial { break-inside: avoid; margin-bottom: var(--lib-gap); } @media (min-width: 750px) { .lib-testimonials[data-layout='grid'] .lib-testimonials__list { grid-template-columns: repeat(min(var(--lib-cols), 2), minmax(0, 1fr)); } .lib-testimonials[data-layout='masonry'] .lib-testimonials__list { column-count: min(var(--lib-cols), 2); } } @media (min-width: 990px) { .lib-testimonials[data-layout='grid'] .lib-testimonials__list { grid-template-columns: repeat(var(--lib-cols), minmax(0, 1fr)); } .lib-testimonials[data-layout='masonry'] .lib-testimonials__list { column-count: var(--lib-cols); } } /* Scroll row: cards snap; a slice of the next card shows that the row continues. */ .lib-testimonials[data-layout='scroll'] .lib-testimonials__list { --lib-per-view: calc(var(--lib-cols-m) * 1); display: grid; grid-auto-flow: column; grid-auto-columns: calc((100% - (var(--lib-per-view) - 1) * var(--lib-gap)) / var(--lib-per-view) - 2.5rem); gap: var(--lib-gap); overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scroll-padding-inline: 0; scrollbar-width: none; padding-block: 2px; } .lib-testimonials[data-layout='scroll'] .lib-testimonials__list::-webkit-scrollbar { display: none; } .lib-testimonials[data-layout='scroll'] .lib-testimonial { scroll-snap-align: start; } @media (min-width: 750px) { .lib-testimonials[data-layout='scroll'] .lib-testimonials__list { --lib-per-view: min(var(--lib-cols), 2); } } @media (min-width: 990px) { .lib-testimonials[data-layout='scroll'] .lib-testimonials__list { --lib-per-view: var(--lib-cols); grid-auto-columns: calc((100% - (var(--lib-per-view) - 1) * var(--lib-gap)) / var(--lib-per-view)); } } .lib-testimonials__list:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 4px; border-radius: 4px; } .lib-testimonials__nav { display: none; justify-content: flex-end; gap: 0.5rem; margin-top: 1.25rem; } .lib-testimonials__body[data-overflow] .lib-testimonials__nav { display: flex; } .lib-testimonials__arrow { display: inline-flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; padding: 0; border: 1px solid color-mix(in srgb, var(--lib-text) 22%, transparent); border-radius: 50%; background: transparent; color: var(--lib-text); cursor: pointer; transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease; } .lib-testimonials__arrow:hover:not(:disabled) { background: var(--lib-accent); border-color: var(--lib-accent); color: var(--lib-accent-text); } .lib-testimonials__arrow:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; } .lib-testimonials__arrow:disabled { opacity: 0.35; cursor: default; } /* Card */ .lib-testimonial { position: relative; display: flex; flex-direction: column; gap: 1.1rem; margin: 0; padding: clamp(1.25rem, 2.5vw, 1.75rem); border-radius: var(--lib-radius-card); min-width: 0; } .lib-testimonials[data-card='solid'] .lib-testimonial { background: var(--lib-card); } .lib-testimonials[data-card='border'] .lib-testimonial { box-shadow: inset 0 0 0 1px var(--lib-border); } .lib-testimonials[data-card='none'] .lib-testimonial { padding-inline: 0; } .lib-testimonials[data-quote-mark] .lib-testimonial::before { content: '\201C'; position: absolute; inset-block-start: 0.6rem; inset-inline-end: 1.25rem; font-family: Georgia, 'Times New Roman', serif; font-size: 4.5rem; line-height: 1; color: var(--lib-accent); opacity: 0.18; pointer-events: none; } .lib-testimonial__stars { display: flex; gap: 2px; color: var(--lib-star); } .lib-testimonial__star { display: block; } .lib-testimonial__star--off { color: color-mix(in srgb, var(--lib-text) 18%, transparent); } .lib-testimonial__quote { margin: 0; font-size: 1.05rem; line-height: 1.6; } .lib-testimonial__quote > :first-child { margin-top: 0; } .lib-testimonial__quote > :last-child { margin-bottom: 0; } .lib-testimonial__author { display: flex; align-items: center; gap: 0.75rem; margin-top: auto; } .lib-testimonials[data-layout='masonry'] .lib-testimonial__author { margin-top: 0; } .lib-testimonial__avatar { display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; border-radius: 50%; overflow: hidden; background: var(--lib-accent); color: var(--lib-accent-text); font-size: 0.9rem; font-weight: 600; letter-spacing: 0.02em; } .lib-testimonial__avatar-img { display: block; width: 100%; height: 100%; object-fit: cover; } .lib-testimonial__meta { display: flex; flex-direction: column; min-width: 0; gap: 0.1rem; font-size: 0.9rem; line-height: 1.35; } .lib-testimonial__name { font-weight: 600; } .lib-testimonial__role { opacity: 0.65; } .lib-testimonial__product { color: inherit; font-size: 0.85rem; opacity: 0.8; text-underline-offset: 0.2em; } a.lib-testimonial__product:hover { opacity: 1; } a.lib-testimonial__product:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; border-radius: 2px; } .lib-testimonials__actions { display: flex; justify-content: center; margin-top: 2.5rem; } .lib-testimonials__actions[data-align='left'] { justify-content: flex-start; } .lib-testimonials__actions[data-align='right'] { justify-content: flex-end; } @media (max-width: 749px) { .lib-testimonials__actions { margin-top: 2rem; } .lib-testimonials__actions[data-align-m='left'] { justify-content: flex-start; } .lib-testimonials__actions[data-align-m='center'] { justify-content: center; } .lib-testimonials__actions[data-align-m='right'] { justify-content: flex-end; } } /* Motion: cards fade up as they scroll into view, where the browser supports scroll-driven animations. */ @supports (animation-timeline: view()) { @media (prefers-reduced-motion: no-preference) { .lib-testimonials[data-animate]:not([data-layout='scroll']) .lib-testimonial { animation: lib-testimonials-rise linear both; animation-timeline: view(); animation-range: entry 0% entry 60%; } } } @keyframes lib-testimonials-rise { from { opacity: 0; transform: translateY(24px); } } @media (prefers-reduced-motion: reduce) { .lib-testimonials__arrow { transition: none; } } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-testimonials-scroller')) { customElements.define( 'lib-testimonials-scroller', class extends HTMLElement { connectedCallback() { this.track = this.querySelector('[data-track]'); this.buttons = this.querySelectorAll('.lib-testimonials__arrow'); if (!this.track || !this.buttons.length || this.observer) return; this.onScroll = () => this.update(); this.track.addEventListener('scroll', this.onScroll, { passive: true }); this.buttons.forEach((button) => button.addEventListener('click', () => this.step(Number(button.dataset.dir)))); this.observer = new ResizeObserver(() => this.update()); this.observer.observe(this.track); this.update(); } disconnectedCallback() { this.observer?.disconnect(); this.observer = null; this.track?.removeEventListener('scroll', this.onScroll); } step(dir) { const card = this.track.firstElementChild; const gap = parseFloat(getComputedStyle(this.track).columnGap) || 0; const width = card ? card.getBoundingClientRect().width + gap : this.track.clientWidth; const rtl = getComputedStyle(this.track).direction === 'rtl'; const smooth = !matchMedia('(prefers-reduced-motion: reduce)').matches; this.track.scrollBy({ left: dir * width * (rtl ? -1 : 1), behavior: smooth ? 'smooth' : 'auto' }); } update() { const max = this.track.scrollWidth - this.track.clientWidth; const x = Math.abs(this.track.scrollLeft); this.toggleAttribute('data-overflow', max > 2); const [prev, next] = this.buttons; if (prev) prev.disabled = x <= 2; if (next) next.disabled = x >= max - 2; } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Testimonials", "tag": "section", "class": "lib-section", "max_blocks": 12, "settings": [ { "type": "header", "content": "Content" }, { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Loved in homes like yours" }, { "type": "select", "id": "heading_tag", "label": "Heading tag", "info": "Use H1 only when this is the page's main heading.", "options": [ { "value": "h1", "label": "H1" }, { "value": "h2", "label": "H2" }, { "value": "h3", "label": "H3" }, { "value": "p", "label": "Paragraph" } ], "default": "h2" }, { "type": "select", "id": "heading_size", "label": "Heading size", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" }, { "value": "xlarge", "label": "Extra large" } ], "default": "medium" }, { "type": "text", "id": "summary", "label": "Rating summary", "info": "Optional line under the heading. Use your store's real figures; the default is sample text.", "default": "4.8 average from 1,200 reviews" }, { "type": "richtext", "id": "text", "label": "Intro" }, { "type": "header", "content": "Button" }, { "type": "text", "id": "button_label", "label": "Button label", "default": "Read all reviews" }, { "type": "url", "id": "button_url", "label": "Button link" }, { "type": "select", "id": "button_style", "label": "Button style", "options": [ { "value": "primary", "label": "Solid" }, { "value": "outline", "label": "Outline" }, { "value": "text", "label": "Text link" } ], "default": "outline" }, { "type": "checkbox", "id": "button_new_tab", "label": "Open link in a new tab", "default": false }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "layout", "label": "Layout", "options": [ { "value": "grid", "label": "Grid" }, { "value": "masonry", "label": "Masonry" }, { "value": "scroll", "label": "Scrolling row" } ], "default": "masonry" }, { "type": "range", "id": "columns", "label": "Columns on desktop", "info": "In the scrolling row, the number of cards in view.", "min": 1, "max": 4, "step": 1, "default": 3 }, { "type": "select", "id": "columns_mobile", "label": "Columns on mobile", "options": [ { "value": "1", "label": "1" }, { "value": "2", "label": "2" } ], "default": "1" }, { "type": "select", "id": "card_style", "label": "Card style", "options": [ { "value": "solid", "label": "Filled" }, { "value": "border", "label": "Outline" }, { "value": "none", "label": "No card" } ], "default": "solid" }, { "type": "range", "id": "card_radius", "label": "Card corner radius", "min": 0, "max": 32, "step": 2, "unit": "px", "default": 16 }, { "type": "checkbox", "id": "quote_mark", "label": "Show a large quote mark", "default": true }, { "type": "select", "id": "section_width", "label": "Width", "options": [ { "value": "page", "label": "Page width" }, { "value": "full", "label": "Full width" } ], "default": "page" }, { "type": "text_alignment", "id": "text_alignment", "label": "Heading alignment on desktop", "default": "center" }, { "type": "select", "id": "text_alignment_mobile", "label": "Heading alignment on mobile", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" }, { "value": "right", "label": "Right" } ], "default": "left" }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Background", "default": "#F5F5F4" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#1C1917" }, { "type": "color", "id": "color_card", "label": "Card background", "default": "#FFFFFF", "visible_if": "{{ section.settings.card_style == 'solid' }}" }, { "type": "color", "id": "color_border", "label": "Card border", "default": "#E7E5E4", "visible_if": "{{ section.settings.card_style == 'border' }}" }, { "type": "color", "id": "color_accent", "label": "Accent (initials, quote mark, buttons)", "default": "#44403C" }, { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FAFAF9" }, { "type": "color", "id": "color_star", "label": "Stars", "default": "#D97706" }, { "type": "header", "content": "Spacing and visibility" }, { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 80 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 80 }, { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 48 }, { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 48 }, { "type": "select", "id": "show_on", "label": "Show on", "options": [ { "value": "all", "label": "All devices" }, { "value": "desktop", "label": "Desktop only" }, { "value": "mobile", "label": "Mobile only" } ], "default": "all" }, { "type": "text", "id": "anchor_id", "label": "Anchor ID", "info": "Optional. Link to this section with #your-id." }, { "type": "header", "content": "Motion" }, { "type": "checkbox", "id": "animate", "label": "Fade cards in on scroll", "info": "Grid and masonry layouts, in browsers that support scroll-driven animations.", "default": false } ], "blocks": [ { "type": "quote", "name": "Testimonial", "limit": 12, "settings": [ { "type": "richtext", "id": "quote", "label": "Quote", "default": "

The glaze is even nicer in person. It has become the mug everyone reaches for first.

" }, { "type": "text", "id": "name", "label": "Name", "default": "Jamie R." }, { "type": "text", "id": "role", "label": "Role or location", "default": "Portland, OR" }, { "type": "select", "id": "rating", "label": "Star rating", "options": [ { "value": "0", "label": "No stars" }, { "value": "1", "label": "1 star" }, { "value": "2", "label": "2 stars" }, { "value": "3", "label": "3 stars" }, { "value": "4", "label": "4 stars" }, { "value": "5", "label": "5 stars" } ], "default": "5" }, { "type": "image_picker", "id": "avatar", "label": "Photo", "info": "Optional. Without a photo, the initials of the name are shown." }, { "type": "text", "id": "product_label", "label": "Product label", "info": "Optional, for example the product they bought." }, { "type": "url", "id": "product_url", "label": "Product link", "visible_if": "{{ block.settings.product_label != blank }}" } ] } ], "presets": [ { "name": "Lib · Testimonials", "blocks": [ { "type": "quote", "settings": { "quote": "

The glaze is even nicer in person. It has become the mug everyone reaches for first.

", "name": "Jamie Rowe", "role": "Portland, OR", "rating": "5", "product_label": "Speckled stoneware mug" } }, { "type": "quote", "settings": { "quote": "

I ordered the linen throw on a Monday and it was on my sofa by Thursday. Soft from the first wash, and it has only got better.

", "name": "Priya Nair", "role": "Austin, TX", "rating": "5", "product_label": "Washed linen throw" } }, { "type": "quote", "settings": { "quote": "

Lovely candles with a clean, slow burn.

", "name": "Tom", "role": "Repeat customer", "rating": "4" } }, { "type": "quote", "settings": { "quote": "

One of the bowls arrived chipped. I sent a photo and a replacement was sent the same day, no questions asked. That is why I keep coming back.

", "name": "Elena Martín", "role": "Chicago, IL", "rating": "5" } }, { "type": "quote", "settings": { "quote": "

Heavier and better made than I expected for the price. The colors match the photos.

", "name": "Marcus Lee", "role": "Seattle, WA", "rating": "5", "product_label": "Oak serving board" } }, { "type": "quote", "settings": { "quote": "

Gift-wrapped beautifully. My sister loved her vase.

", "name": "Hannah Brooks", "role": "Denver, CO", "rating": "5" } } ] } ] } {% endschema %}