← All sections

Review Wall

A living wall of verified reviews under a big, honest score and rating breakdown.

FreeSocial proof38 settingsInstall
Full screen ↗

Install

One file: sections/mf-review-wall.liquid. It appears in the theme editor as EX · Review wall.

  1. Connect EXPERAISE Sections to Claude (once). In Claude Code:
    terminal
    claude mcp add --transport http experaise-sections https://sections.experaise.com/api/mcp
    In the Claude app: Settings → Connectors → Add custom connector → https://sections.experaise.com/api/mcp. Full setup
  2. Ask Claude. It fetches sections/mf-review-wall.liquid, pushes only that file to your theme with Shopify CLI, and tells you where to find it.
    prompt
    Install the EXPERAISE Sections section "Review Wall" (review-wall) on my Shopify store your-store.myshopify.com. Put it on an unpublished copy of my live theme, then tell me how to add it in the theme editor.
  3. Customize. Online Store → Themes → Customize → Add section → EX · Review wall. Every text, color and product is a setting.
sections/mf-review-wall.liquidDownload
{%- liquid
  assign s = section.settings

  comment
    Aggregate score: the product's review metafields on product pages (when enabled), else the manual settings.
  endcomment
  assign score = s.score
  assign count = s.review_count
  if s.use_product_rating and product != blank
    assign mf_rating = product.metafields.reviews.rating.value
    if mf_rating != blank and mf_rating.scale_max > 0
      assign score = mf_rating.rating | times: 5.0 | divided_by: mf_rating.scale_max
      assign mf_count = product.metafields.reviews.rating_count.value
      if mf_count != blank
        assign count = mf_count
      endif
    endif
  endif
  assign score = score | round: 1
  assign score_str = score | append: ''
  unless score_str contains '.'
    assign score_str = score_str | append: '.0'
  endunless
  assign score_pct = score | times: 20 | round

  comment
    Thousands separators for the review count (Liquid has no number formatting filter).
  endcomment
  assign count_str = ''
  if count != blank
    assign count_str = count | append: ''
    if count >= 1000000
      assign c_m = count | divided_by: 1000000
      assign c_t = count | modulo: 1000000 | divided_by: 1000 | plus: 1000 | append: '' | slice: 1, 3
      assign c_r = count | modulo: 1000 | plus: 1000 | append: '' | slice: 1, 3
      assign count_str = c_m | append: ',' | append: c_t | append: ',' | append: c_r
    elsif count >= 1000
      assign c_t = count | divided_by: 1000
      assign c_r = count | modulo: 1000 | plus: 1000 | append: '' | slice: 1, 3
      assign count_str = c_t | append: ',' | append: c_r
    endif
  endif
  assign count_text = s.count_label | replace: '[count]', count_str
  assign score_sr = s.rating_sr | replace: '[rating]', score_str

  comment
    Marquee rows: blocks alternate between rows when two rows are selected.
  endcomment
  assign total = section.blocks.size
  assign row_count = 1
  if s.rows == '2' and total > 1
    assign row_count = 2
  endif
  case s.speed
    when 'slow'
      assign per_card = 10
    when 'fast'
      assign per_card = 5
    else
      assign per_card = 7
  endcase
  assign gradients = 'a,b,c,d,e,f' | split: ','
-%}

<mf-review-wall
  class="mf-reviews"
  style="--mf-reviews-bg: {{ s.color_bg }}; --mf-reviews-fg: {{ s.color_text }}; --mf-reviews-card: {{ s.color_card }}; --mf-reviews-accent: {{ s.color_accent }}; --mf-reviews-feature: {{ s.color_feature }}; --mf-reviews-pt: {{ s.padding_top }}px; --mf-reviews-pb: {{ s.padding_bottom }}px;"
>
  <div class="mf-reviews__glow" aria-hidden="true"></div>

  <div class="mf-reviews__inner mf-reviews__head">
    <div class="mf-reviews__copy">
      {%- if s.eyebrow != blank -%}
        <p class="mf-reviews__eyebrow"><i aria-hidden="true"></i>{{ s.eyebrow | escape }}</p>
      {%- endif -%}
      {%- if s.heading != blank -%}
        <h2 class="mf-reviews__title">{{ s.heading }}</h2>
      {%- endif -%}
      {%- if s.text != blank -%}
        <div class="mf-reviews__text">{{ s.text }}</div>
      {%- endif -%}
      {%- if s.button_label != blank -%}
        <a class="mf-reviews__button" href="{{ s.button_link | default: routes.all_products_collection_url }}">
          {{- s.button_label | escape -}}
          <svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true"><path d="M4 10h11m-4-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </a>
      {%- endif -%}
    </div>

    <div class="mf-reviews__score">
      <div class="mf-reviews__score-top">
        <p class="mf-reviews__big" aria-hidden="true">
          {%- assign score_chars = score_str | split: '' -%}
          {%- for ch in score_chars -%}
            {%- if ch == '.' -%}
              <span class="mf-reviews__dot">.</span>
            {%- else -%}
              <span class="mf-reviews__digit" style="--mf-reviews-d: {{ ch }}; --mf-reviews-i: {{ forloop.index0 }};">
                <span class="mf-reviews__strip">
                  <span></span>
                  {%- for n in (0..9) -%}<span>{{ n }}</span>{%- endfor -%}
                </span>
              </span>
            {%- endif -%}
          {%- endfor -%}
          <small>/5</small>
        </p>
        <div class="mf-reviews__score-meta">
          {%- if s.score_label != blank -%}
            <p class="mf-reviews__verdict">{{ s.score_label | escape }}</p>
          {%- endif -%}
          <span class="mf-reviews__stars mf-reviews__stars--lg" style="--mf-reviews-fill: {{ score_pct }}%;" aria-hidden="true"></span>
          <p class="mf-reviews__sr">{{ score_sr | escape }}</p>
          {%- if count_str != blank and count_text != blank -%}
            <p class="mf-reviews__count">{{ count_text | escape }}</p>
          {%- endif -%}
        </div>
      </div>

      {%- if s.show_breakdown -%}
        <ul class="mf-reviews__bars" role="list">
          {%- for star in (1..5) reversed -%}
            {%- liquid
              case star
                when 5
                  assign pct = s.pct_5
                when 4
                  assign pct = s.pct_4
                when 3
                  assign pct = s.pct_3
                when 2
                  assign pct = s.pct_2
                else
                  assign pct = s.pct_1
              endcase
            -%}
            <li class="mf-reviews__bar-row" style="--mf-reviews-pct: {{ pct }}%; --mf-reviews-i: {{ forloop.index0 }};">
              <span class="mf-reviews__bar-label">{{ star }}<span aria-hidden="true">★</span></span>
              <span class="mf-reviews__bar" aria-hidden="true"><i></i></span>
              <span class="mf-reviews__bar-pct">{{ pct }}%</span>
            </li>
          {%- endfor -%}
        </ul>
      {%- endif -%}
    </div>
  </div>

  {%- if total > 0 -%}
    <div class="mf-reviews__marquee">
      {%- for row in (1..row_count) -%}
        {%- liquid
          if row_count == 2
            assign half = total | divided_by: 2
            if row == 1
              assign in_row = total | minus: half
            else
              assign in_row = half
            endif
          else
            assign in_row = total
          endif
          assign reps = 7 | plus: in_row | minus: 1 | divided_by: in_row
          assign group_cards = in_row | times: reps
          assign duration = group_cards | times: per_card
          assign row_label = s.list_label | escape
          if row_count == 2
            assign row_label = row_label | append: ' · ' | append: row | append: '/2'
          endif
        -%}
        <div
          class="mf-reviews__row mf-reviews__row--{% if row == 2 %}right{% else %}left{% endif %}"
          role="region"
          aria-label="{{ row_label }}"
          tabindex="0"
          style="--mf-reviews-dur: {{ duration }}s;"
        >
          <div class="mf-reviews__track">
            {%- for copy in (1..2) -%}
              <ul class="mf-reviews__group" role="list"{% if copy == 2 %} aria-hidden="true" data-mf-clone{% endif %}>
                {%- for rep in (1..reps) -%}
                  {%- for block in section.blocks -%}
                    {%- liquid
                      assign b = block.settings
                      assign parity = forloop.index | modulo: 2
                      assign show = true
                      if row_count == 2
                        if row == 1 and parity == 0
                          assign show = false
                        elsif row == 2 and parity == 1
                          assign show = false
                        endif
                      endif
                      if b.quote == blank
                        assign show = false
                      endif
                      assign clone = false
                      if copy > 1 or rep > 1
                        assign clone = true
                      endif
                    -%}
                    {%- if show -%}
                      {%- liquid
                        assign words = b.author | strip | split: ' '
                        assign initials = words.first | slice: 0 | upcase
                        if words.size > 1
                          assign last_initial = words.last | slice: 0 | upcase
                          assign initials = initials | append: last_initial
                        endif
                        assign g_index = forloop.index0 | modulo: 6
                        assign rating_str = b.rating | append: ''
                        assign review_sr = s.rating_sr | replace: '[rating]', rating_str
                      -%}
                      <li class="mf-reviews__item"{% if clone %} aria-hidden="true" data-mf-clone{% endif %}>
                        <figure
                          class="mf-reviews__card{% if b.highlight %} mf-reviews__card--feature{% endif %}"
                          {% unless clone %}{{ block.shopify_attributes }}{% endunless %}
                        >
                          <div class="mf-reviews__card-top">
                            <span class="mf-reviews__stars" style="--mf-reviews-fill: {{ b.rating | times: 20 }}%;" aria-hidden="true"></span>
                            <span class="mf-reviews__sr">{{ review_sr | escape }}</span>
                            {%- if b.verified and s.verified_label != blank -%}
                              <span class="mf-reviews__verified">
                                <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="currentColor"/><path d="m4.8 8.2 2.1 2.1 4.3-4.5" fill="none" stroke="var(--mf-reviews-tick, #fff)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                {{- s.verified_label | escape -}}
                              </span>
                            {%- endif -%}
                          </div>
                          {%- if b.title != blank -%}
                            <p class="mf-reviews__card-title">{{ b.title | escape }}</p>
                          {%- endif -%}
                          <blockquote class="mf-reviews__quote">
                            <p>{{ b.quote | escape | newline_to_br }}</p>
                          </blockquote>
                          <figcaption class="mf-reviews__author">
                            {%- if b.image != blank -%}
                              {{
                                b.image
                                | image_url: width: 120
                                | image_tag:
                                  loading: 'lazy',
                                  sizes: '44px',
                                  widths: '60, 120',
                                  class: 'mf-reviews__avatar mf-reviews__avatar--img',
                                  alt: ''
                              }}
                            {%- else -%}
                              <span class="mf-reviews__avatar mf-reviews__avatar--{{ gradients[g_index] }}" aria-hidden="true">{{ initials }}</span>
                            {%- endif -%}
                            <span class="mf-reviews__who">
                              {%- if b.author != blank -%}
                                <span class="mf-reviews__name">{{ b.author | escape }}</span>
                              {%- endif -%}
                              {%- if b.detail != blank -%}
                                <span class="mf-reviews__detail">{{ b.detail | escape }}</span>
                              {%- endif -%}
                            </span>
                          </figcaption>
                        </figure>
                      </li>
                    {%- endif -%}
                  {%- endfor -%}
                {%- endfor -%}
              </ul>
            {%- endfor -%}
          </div>
        </div>
      {%- endfor -%}
    </div>
  {%- endif -%}

  {%- if s.footnote != blank or total > 0 -%}
    <div class="mf-reviews__inner mf-reviews__foot">
      {%- if s.footnote != blank -%}
        <p class="mf-reviews__footnote">
          <svg viewBox="0 0 20 20" width="16" height="16" aria-hidden="true"><path d="M10 2.5 4 5v4.6c0 3.7 2.5 6.6 6 7.9 3.5-1.3 6-4.2 6-7.9V5l-6-2.5Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="m7.3 10 1.9 1.9 3.6-3.8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
          {{- s.footnote | escape -}}
        </p>
      {%- endif -%}
      {%- if total > 0 and s.pause_label != blank -%}
        <button type="button" class="mf-reviews__pause" aria-pressed="false" data-mf-pause>
          <svg class="mf-reviews__pause-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="3.5" y="3" width="3" height="10" rx="1" fill="currentColor"/><rect x="9.5" y="3" width="3" height="10" rx="1" fill="currentColor"/></svg>
          <svg class="mf-reviews__play-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M5 3.2v9.6a.6.6 0 0 0 .9.5l7.6-4.8a.6.6 0 0 0 0-1L5.9 2.7a.6.6 0 0 0-.9.5Z" fill="currentColor"/></svg>
          <span>{{ s.pause_label | escape }}</span>
        </button>
      {%- endif -%}
    </div>
  {%- endif -%}
</mf-review-wall>

{% stylesheet %}
  .mf-reviews {
    position: relative;
    isolation: isolate;
    display: block;
    overflow: hidden;
    padding: var(--mf-reviews-pt, 88px) 0 var(--mf-reviews-pb, 72px);
    background: var(--mf-reviews-bg, #f2ede6);
    color: var(--mf-reviews-fg, #1c1814);
    font-family: inherit;
    --mf-reviews-line: color-mix(in srgb, var(--mf-reviews-fg, #1c1814) 11%, transparent);
    --mf-reviews-muted: color-mix(in srgb, var(--mf-reviews-fg, #1c1814) 64%, transparent);
  }
  .mf-reviews *,
  .mf-reviews *::before,
  .mf-reviews *::after {
    box-sizing: border-box;
  }
  .mf-reviews::after {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0;
    pointer-events: none;
    opacity: 0.5;
    background-image: radial-gradient(color-mix(in srgb, var(--mf-reviews-fg, #1c1814) 9%, transparent) 0.6px, transparent 0.9px);
    background-size: 5px 5px;
    -webkit-mask-image: linear-gradient(180deg, #000, transparent 55%);
    mask-image: linear-gradient(180deg, #000, transparent 55%);
  }
  .mf-reviews__glow {
    position: absolute;
    z-index: -2;
    inset: 0;
    pointer-events: none;
    background:
      radial-gradient(640px 420px at 88% 6%, color-mix(in srgb, var(--mf-reviews-accent, #b8742a) 22%, transparent), transparent 70%),
      radial-gradient(520px 360px at 6% 92%, color-mix(in srgb, var(--mf-reviews-accent, #b8742a) 12%, transparent), transparent 70%),
      linear-gradient(180deg, color-mix(in srgb, #fff 38%, transparent), transparent 42%);
  }
  .mf-reviews__inner {
    width: min(100% - 40px, 1320px);
    margin: 0 auto;
  }
  .mf-reviews__head {
    display: grid;
    grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
    align-items: end;
    gap: clamp(32px, 6vw, 96px);
    margin-bottom: clamp(32px, 4.4vw, 56px);
  }
  .mf-reviews__eyebrow {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 22px;
    color: inherit;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    opacity: 0.8;
  }
  .mf-reviews__eyebrow i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--mf-reviews-accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--mf-reviews-accent) 20%, transparent);
  }
  .mf-reviews__title {
    max-width: 12ch;
    margin: 0;
    color: inherit;
    font-family: inherit;
    font-size: clamp(40px, 6vw, 84px);
    font-weight: 600;
    line-height: 0.96;
    letter-spacing: -0.045em;
    text-transform: none;
    text-wrap: balance;
  }
  .mf-reviews__title em {
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    font-weight: 400;
    font-style: italic;
    letter-spacing: -0.035em;
    color: color-mix(in srgb, var(--mf-reviews-accent) 70%, var(--mf-reviews-fg));
  }
  .mf-reviews__text {
    max-width: 46ch;
    margin-top: 22px;
    color: var(--mf-reviews-muted);
    font-size: 17px;
    line-height: 1.55;
    letter-spacing: 0;
  }
  .mf-reviews__text p {
    margin: 0;
  }
  .mf-reviews__button {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    min-height: 50px;
    margin-top: 30px;
    padding: 0 24px;
    border-radius: 999px;
    background: var(--mf-reviews-fg);
    color: var(--mf-reviews-bg);
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0;
    text-decoration: none;
    box-shadow: 0 14px 30px -14px color-mix(in srgb, var(--mf-reviews-fg) 70%, transparent);
    transition: transform 0.25s ease, box-shadow 0.25s ease;
  }
  .mf-reviews__button svg {
    transition: transform 0.25s ease;
  }
  .mf-reviews__button:hover {
    transform: translateY(-2px);
    box-shadow: 0 20px 36px -16px color-mix(in srgb, var(--mf-reviews-fg) 80%, transparent);
  }
  .mf-reviews__button:hover svg {
    transform: translateX(4px);
  }

  /* Aggregate scorecard */
  .mf-reviews__score {
    position: relative;
    padding: clamp(24px, 3vw, 36px);
    border: 1px solid color-mix(in srgb, #fff 70%, transparent);
    border-radius: 28px;
    background: linear-gradient(160deg, color-mix(in srgb, #fff 78%, transparent), color-mix(in srgb, #fff 42%, transparent));
    box-shadow:
      0 1px 0 color-mix(in srgb, #fff 80%, transparent) inset,
      0 30px 60px -36px color-mix(in srgb, var(--mf-reviews-fg) 45%, transparent),
      0 0 0 1px var(--mf-reviews-line);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }
  .mf-reviews__score-top {
    display: flex;
    align-items: center;
    gap: clamp(18px, 2.4vw, 30px);
  }
  .mf-reviews__big {
    display: flex;
    align-items: flex-start;
    flex-shrink: 0;
    margin: 0;
    color: inherit;
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    font-size: clamp(76px, 8.4vw, 118px);
    font-weight: 400;
    line-height: 0.8;
    letter-spacing: -0.05em;
    font-variant-numeric: lining-nums tabular-nums;
  }
  .mf-reviews__digit {
    display: block;
    height: 0.86em;
    overflow: hidden;
  }
  .mf-reviews__strip {
    display: block;
    transform: translateY(calc((var(--mf-reviews-d, 0) + 1) * -1em));
  }
  .mf-reviews__strip span {
    display: block;
    height: 1em;
    line-height: 0.86;
  }
  .mf-reviews--js .mf-reviews__strip {
    transform: translateY(0);
    transition: transform 1.5s cubic-bezier(0.16, 0.84, 0.24, 1);
    transition-delay: calc(0.15s + var(--mf-reviews-i, 0) * 0.14s);
  }
  .mf-reviews--js.mf-reviews--in .mf-reviews__strip {
    transform: translateY(calc((var(--mf-reviews-d, 0) + 1) * -1em));
  }
  .mf-reviews__dot {
    display: block;
    line-height: 0.86;
  }
  .mf-reviews--js .mf-reviews__dot {
    opacity: 0;
    transition: opacity 0.6s ease 0.5s;
  }
  .mf-reviews--js.mf-reviews--in .mf-reviews__dot {
    opacity: 1;
  }
  .mf-reviews__big small {
    margin: 0.06em 0 0 0.08em;
    color: var(--mf-reviews-muted);
    font-family: inherit;
    font-size: 0.24em;
    letter-spacing: 0;
  }
  .mf-reviews__score-meta {
    display: grid;
    justify-items: start;
    gap: 8px;
    min-width: 0;
  }
  .mf-reviews__verdict {
    margin: 0;
    color: inherit;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: 0.16em;
    text-transform: uppercase;
  }
  .mf-reviews__count {
    margin: 0;
    color: var(--mf-reviews-muted);
    font-size: 14px;
    line-height: 1.4;
    letter-spacing: 0;
  }

  /* Stars: a gradient fill masked by five star shapes (supports partial values) */
  .mf-reviews__stars {
    --mf-reviews-sw: 17px;
    --mf-reviews-sh: 13px;
    display: inline-block;
    flex-shrink: 0;
    width: calc(var(--mf-reviews-sw) * 5);
    height: var(--mf-reviews-sh);
    background: linear-gradient(90deg, var(--mf-reviews-accent) var(--mf-reviews-fill, 100%), color-mix(in srgb, var(--mf-reviews-fg) 16%, transparent) var(--mf-reviews-fill, 100%));
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 14'%3E%3Cpath d='M7.5 0l2.2 4.6 5 .6-3.7 3.4 1 5-4.5-2.5L3 13.6l1-5L.3 5.2l5-.6z'/%3E%3C/svg%3E") repeat-x left center / var(--mf-reviews-sw) var(--mf-reviews-sh);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 14'%3E%3Cpath d='M7.5 0l2.2 4.6 5 .6-3.7 3.4 1 5-4.5-2.5L3 13.6l1-5L.3 5.2l5-.6z'/%3E%3C/svg%3E") repeat-x left center / var(--mf-reviews-sw) var(--mf-reviews-sh);
  }
  .mf-reviews__stars--lg {
    --mf-reviews-sw: 26px;
    --mf-reviews-sh: 20px;
  }
  .mf-reviews--js .mf-reviews__stars--lg {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.2s cubic-bezier(0.3, 0.7, 0.2, 1) 0.45s;
  }
  .mf-reviews--js.mf-reviews--in .mf-reviews__stars--lg {
    clip-path: inset(0 0 0 0);
  }

  /* Breakdown bars */
  .mf-reviews__bars {
    display: grid;
    gap: 9px;
    margin: 26px 0 0;
    padding: 22px 0 0;
    border-top: 1px solid var(--mf-reviews-line);
    list-style: none;
  }
  .mf-reviews__bar-row {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) 40px;
    align-items: center;
    gap: 12px;
    margin: 0;
    font-size: 13px;
    line-height: 1;
  }
  .mf-reviews__bar-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  .mf-reviews__bar-label span {
    color: var(--mf-reviews-accent);
    font-size: 12px;
  }
  .mf-reviews__bar {
    position: relative;
    height: 6px;
    overflow: hidden;
    border-radius: 999px;
    background: color-mix(in srgb, var(--mf-reviews-fg) 9%, transparent);
  }
  .mf-reviews__bar i {
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--mf-reviews-pct, 0%);
    border-radius: inherit;
    background: linear-gradient(90deg, color-mix(in srgb, var(--mf-reviews-accent) 70%, #fff), var(--mf-reviews-accent));
    transform-origin: left center;
  }
  .mf-reviews--js .mf-reviews__bar i {
    transform: scaleX(0);
    transition: transform 1.1s cubic-bezier(0.2, 0.75, 0.2, 1);
    transition-delay: calc(0.25s + var(--mf-reviews-i, 0) * 90ms);
  }
  .mf-reviews--js.mf-reviews--in .mf-reviews__bar i {
    transform: none;
  }
  .mf-reviews__bar-pct {
    color: var(--mf-reviews-muted);
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  /* Marquee */
  .mf-reviews__marquee {
    display: grid;
    gap: 2px;
  }
  .mf-reviews__row {
    position: relative;
    overflow: hidden;
    padding: 12px 0 26px;
    outline: none;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  }
  .mf-reviews__row:focus-visible .mf-reviews__card {
    border-color: color-mix(in srgb, var(--mf-reviews-accent) 60%, transparent);
  }
  .mf-reviews__row:focus-visible::before {
    content: "";
    position: absolute;
    z-index: 2;
    inset: 4px 9% 18px;
    border: 2px solid var(--mf-reviews-accent);
    border-radius: 26px;
    pointer-events: none;
  }
  .mf-reviews__track {
    display: flex;
    width: max-content;
    animation: mf-reviews-left var(--mf-reviews-dur, 56s) linear infinite;
    will-change: transform;
  }
  .mf-reviews__row--right .mf-reviews__track {
    animation-name: mf-reviews-right;
  }
  .mf-reviews__row:hover .mf-reviews__track,
  .mf-reviews__row:focus-within .mf-reviews__track,
  .mf-reviews[data-paused="true"] .mf-reviews__track,
  .mf-reviews[data-mf-offscreen] .mf-reviews__track {
    animation-play-state: paused;
  }
  .mf-reviews__group {
    display: flex;
    align-items: stretch;
    gap: 20px;
    margin: 0;
    padding: 0 20px 0 0;
    list-style: none;
  }
  .mf-reviews__item {
    display: flex;
    margin: 0;
  }

  /* Review card */
  .mf-reviews__card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: clamp(276px, 78vw, 372px);
    margin: 0;
    padding: 26px 26px 22px;
    overflow: hidden;
    border: 1px solid var(--mf-reviews-line);
    border-radius: 22px;
    background: var(--mf-reviews-card, #fffcf8);
    color: var(--mf-reviews-fg);
    box-shadow:
      0 1px 0 color-mix(in srgb, #fff 90%, transparent) inset,
      0 22px 44px -30px color-mix(in srgb, var(--mf-reviews-fg) 50%, transparent);
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 0.35s ease, border-color 0.35s ease;
  }
  .mf-reviews__card:hover {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--mf-reviews-accent) 40%, transparent);
    box-shadow:
      0 1px 0 color-mix(in srgb, #fff 90%, transparent) inset,
      0 30px 50px -28px color-mix(in srgb, var(--mf-reviews-fg) 55%, transparent);
  }
  .mf-reviews__card--feature {
    --mf-reviews-fg: #f7f0e6;
    --mf-reviews-muted: rgba(247, 240, 230, 0.66);
    --mf-reviews-line: rgba(255, 255, 255, 0.12);
    --mf-reviews-tick: var(--mf-reviews-feature);
    background:
      radial-gradient(260px 180px at 100% 0%, color-mix(in srgb, var(--mf-reviews-accent) 34%, transparent), transparent 70%),
      var(--mf-reviews-feature, #221c17);
    border-color: transparent;
    box-shadow: 0 26px 50px -26px rgba(0, 0, 0, 0.55);
  }
  .mf-reviews__card--feature::after {
    content: "\201C";
    position: absolute;
    z-index: 0;
    right: 16px;
    bottom: 40px;
    height: 0.62em;
    color: var(--mf-reviews-accent);
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    font-size: 190px;
    line-height: 1;
    opacity: 0.2;
    pointer-events: none;
  }
  .mf-reviews__card--feature > * {
    position: relative;
    z-index: 1;
  }
  .mf-reviews__card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 18px;
  }
  .mf-reviews__verified {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--mf-reviews-muted);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.01em;
    white-space: nowrap;
  }
  .mf-reviews__verified svg {
    color: var(--mf-reviews-accent);
  }
  .mf-reviews__card-title {
    margin: 0 0 8px;
    color: inherit;
    font-family: inherit;
    font-size: 17px;
    font-weight: 650;
    line-height: 1.3;
    letter-spacing: -0.015em;
    text-transform: none;
  }
  .mf-reviews__quote {
    margin: 0 0 22px;
    padding: 0;
    border: 0;
    quotes: none;
  }
  .mf-reviews__quote::before,
  .mf-reviews__quote::after {
    content: none;
  }
  .mf-reviews__quote p {
    margin: 0;
    color: color-mix(in srgb, var(--mf-reviews-fg) 82%, transparent);
    font-family: inherit;
    font-size: 15px;
    font-style: normal;
    font-weight: 400;
    line-height: 1.6;
    letter-spacing: 0;
  }
  .mf-reviews__author {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: auto;
    padding-top: 18px;
    border-top: 1px solid var(--mf-reviews-line);
  }
  .mf-reviews__avatar {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    color: #fff;
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.02em;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35), 0 8px 16px -10px rgba(0, 0, 0, 0.6);
  }
  .mf-reviews__avatar--img {
    object-fit: cover;
    background: var(--mf-reviews-line);
  }
  .mf-reviews__avatar--a { background: radial-gradient(circle at 30% 25%, #f6c9a0, #c9773a 60%, #8d4a22); }
  .mf-reviews__avatar--b { background: radial-gradient(circle at 30% 25%, #d7e3c8, #7f9670 60%, #4f6444); }
  .mf-reviews__avatar--c { background: radial-gradient(circle at 30% 25%, #f3c6cf, #c9798a 60%, #8c4455); }
  .mf-reviews__avatar--d { background: radial-gradient(circle at 30% 25%, #c9d6ee, #6c83b5 60%, #3e507c); }
  .mf-reviews__avatar--e { background: radial-gradient(circle at 30% 25%, #e8dcc2, #a88f5c 60%, #6d5a33); }
  .mf-reviews__avatar--f { background: radial-gradient(circle at 30% 25%, #dccdf0, #917abf 60%, #5d4887); }
  .mf-reviews__who {
    display: grid;
    gap: 3px;
    min-width: 0;
  }
  .mf-reviews__name {
    color: inherit;
    font-size: 14px;
    font-weight: 650;
    line-height: 1.2;
  }
  .mf-reviews__detail {
    overflow: hidden;
    color: var(--mf-reviews-muted);
    font-size: 13px;
    line-height: 1.3;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Footer row */
  .mf-reviews__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px 24px;
    margin-top: 14px;
  }
  .mf-reviews__footnote {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    color: var(--mf-reviews-muted);
    font-size: 13px;
    line-height: 1.45;
    letter-spacing: 0;
  }
  .mf-reviews__footnote svg {
    flex-shrink: 0;
    color: var(--mf-reviews-accent);
  }
  .mf-reviews__pause {
    display: none;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    min-height: 40px;
    margin: 0 0 0 auto;
    padding: 0 16px;
    border: 1px solid var(--mf-reviews-line);
    border-radius: 999px;
    background: color-mix(in srgb, #fff 55%, transparent);
    color: inherit;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0;
    text-transform: none;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition: background 0.2s ease, border-color 0.2s ease;
  }
  .mf-reviews--ready .mf-reviews__pause {
    display: inline-flex;
  }
  .mf-reviews__pause:hover {
    border-color: color-mix(in srgb, var(--mf-reviews-fg) 30%, transparent);
    background: #fff;
  }
  .mf-reviews__play-icon,
  .mf-reviews__pause[aria-pressed="true"] .mf-reviews__pause-icon {
    display: none;
  }
  .mf-reviews__pause[aria-pressed="true"] .mf-reviews__play-icon {
    display: block;
  }
  .mf-reviews__button:focus-visible,
  .mf-reviews__pause:focus-visible {
    outline: 2px solid var(--mf-reviews-accent);
    outline-offset: 3px;
  }
  .mf-reviews__sr {
    position: absolute !important;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  @keyframes mf-reviews-left {
    from { transform: translate3d(0, 0, 0); }
    to { transform: translate3d(-50%, 0, 0); }
  }
  @keyframes mf-reviews-right {
    from { transform: translate3d(-50%, 0, 0); }
    to { transform: translate3d(0, 0, 0); }
  }

  @media screen and (max-width: 989px) {
    .mf-reviews__head {
      grid-template-columns: minmax(0, 1fr);
      align-items: start;
    }
    .mf-reviews__score {
      max-width: 560px;
    }
  }
  @media screen and (max-width: 749px) {
    .mf-reviews {
      padding-top: calc(var(--mf-reviews-pt, 88px) * 0.7);
      padding-bottom: calc(var(--mf-reviews-pb, 72px) * 0.7);
    }
    .mf-reviews__title {
      font-size: clamp(38px, 11vw, 56px);
    }
    .mf-reviews__text {
      font-size: 16px;
    }
    .mf-reviews__score {
      border-radius: 22px;
    }
    .mf-reviews__score-top {
      gap: 16px;
    }
    .mf-reviews__stars--lg {
      --mf-reviews-sw: 21px;
      --mf-reviews-sh: 16px;
    }
    .mf-reviews__row {
      -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
      mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
    }
    .mf-reviews__group {
      gap: 14px;
      padding-right: 14px;
    }
    .mf-reviews__card {
      padding: 22px 20px 18px;
      border-radius: 18px;
    }
    .mf-reviews__foot {
      align-items: flex-start;
      margin-top: 8px;
    }
    .mf-reviews__footnote {
      align-items: flex-start;
      font-size: 12px;
    }
  }
  @media (hover: none) {
    .mf-reviews__card:hover {
      transform: none;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .mf-reviews__row {
      overflow-x: auto;
      padding-left: max(20px, (100% - 1320px) / 2);
      padding-right: max(20px, (100% - 1320px) / 2);
      scroll-snap-type: x mandatory;
      scroll-padding: 0 max(20px, (100% - 1320px) / 2);
      scrollbar-width: thin;
      -webkit-mask-image: none;
      mask-image: none;
    }
    .mf-reviews__track {
      animation: none;
      will-change: auto;
    }
    .mf-reviews__item {
      scroll-snap-align: start;
    }
    .mf-reviews [data-mf-clone] {
      display: none;
    }
    .mf-reviews__card,
    .mf-reviews__button,
    .mf-reviews__button svg {
      transition: none;
    }
    .mf-reviews__card:hover,
    .mf-reviews__button:hover,
    .mf-reviews__button:hover svg {
      transform: none;
    }
    .mf-reviews--js .mf-reviews__bar i {
      transform: none;
      transition: none;
    }
    .mf-reviews--js .mf-reviews__strip {
      transform: translateY(calc((var(--mf-reviews-d, 0) + 1) * -1em));
      transition: none;
    }
    .mf-reviews--js .mf-reviews__stars--lg {
      clip-path: none;
      transition: none;
    }
    .mf-reviews--js .mf-reviews__dot {
      opacity: 1;
      transition: none;
    }
    .mf-reviews--ready .mf-reviews__pause {
      display: none;
    }
  }
{% endstylesheet %}

{% javascript %}
  if (!customElements.get('mf-review-wall')) {
    customElements.define(
      'mf-review-wall',
      class extends HTMLElement {
        connectedCallback() {
          this.pauseBtn = this.querySelector('[data-mf-pause]');
          // Entrance (digit roll, star sweep, bars) is CSS-only; reduced motion neutralises it in the stylesheet.
          if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) this.classList.add('mf-reviews--js');
          this.classList.add('mf-reviews--ready');

          this.onPause = () => this.setPaused(this.dataset.paused !== 'true');
          if (this.pauseBtn) this.pauseBtn.addEventListener('click', this.onPause);

          // Theme editor: freeze the wall while a review block is selected.
          this.onSelect = (event) => {
            if (this.contains(event.target)) this.setPaused(true);
          };
          this.onDeselect = (event) => {
            if (this.contains(event.target)) this.setPaused(false);
          };
          document.addEventListener('shopify:block:select', this.onSelect);
          document.addEventListener('shopify:block:deselect', this.onDeselect);

          if ('IntersectionObserver' in window) {
            this.observer = new IntersectionObserver(
              (entries) => {
                const entry = entries[entries.length - 1];
                this.toggleAttribute('data-mf-offscreen', !entry.isIntersecting);
                if (entry.isIntersecting && entry.intersectionRatio >= 0.15) this.reveal();
              },
              { threshold: [0, 0.15] }
            );
            this.observer.observe(this);
          } else {
            this.reveal();
          }
        }

        disconnectedCallback() {
          if (this.pauseBtn) this.pauseBtn.removeEventListener('click', this.onPause);
          document.removeEventListener('shopify:block:select', this.onSelect);
          document.removeEventListener('shopify:block:deselect', this.onDeselect);
          if (this.observer) this.observer.disconnect();
        }

        setPaused(paused) {
          this.dataset.paused = paused ? 'true' : 'false';
          if (this.pauseBtn) this.pauseBtn.setAttribute('aria-pressed', paused ? 'true' : 'false');
        }

        reveal() {
          if (this.revealed) return;
          this.revealed = true;
          this.classList.add('mf-reviews--in');
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "EX · Review wall",
  "tag": "section",
  "class": "mf-section",
  "disabled_on": {
    "groups": ["header", "footer"]
  },
  "max_blocks": 24,
  "settings": [
    {
      "type": "header",
      "content": "Content"
    },
    {
      "type": "text",
      "id": "eyebrow",
      "label": "Eyebrow",
      "default": "Customer reviews"
    },
    {
      "type": "inline_richtext",
      "id": "heading",
      "label": "Heading",
      "default": "Real skin, <em>real words.</em>"
    },
    {
      "type": "richtext",
      "id": "text",
      "label": "Text",
      "default": "<p>Every review comes from a verified order and is published unedited. This is what people notice after week two.</p>"
    },
    {
      "type": "text",
      "id": "button_label",
      "label": "Button label",
      "default": "Shop the bestsellers"
    },
    {
      "type": "url",
      "id": "button_link",
      "label": "Button link"
    },
    {
      "type": "header",
      "content": "Rating summary"
    },
    {
      "type": "checkbox",
      "id": "use_product_rating",
      "label": "Use the product's rating on product pages",
      "info": "Reads the reviews.rating and reviews.rating_count metafields written by most review apps. Other pages use the values below.",
      "default": true
    },
    {
      "type": "range",
      "id": "score",
      "label": "Average rating",
      "min": 1,
      "max": 5,
      "step": 0.1,
      "default": 4.8
    },
    {
      "type": "text",
      "id": "score_label",
      "label": "Rating label",
      "default": "Excellent"
    },
    {
      "type": "number",
      "id": "review_count",
      "label": "Number of reviews",
      "default": 2340
    },
    {
      "type": "text",
      "id": "count_label",
      "label": "Review count text",
      "info": "[count] is replaced by the number of reviews.",
      "default": "Based on [count] verified reviews"
    },
    {
      "type": "checkbox",
      "id": "show_breakdown",
      "label": "Show rating breakdown",
      "default": true
    },
    {
      "type": "range",
      "id": "pct_5",
      "label": "5-star share",
      "min": 0,
      "max": 100,
      "step": 1,
      "unit": "%",
      "default": 86
    },
    {
      "type": "range",
      "id": "pct_4",
      "label": "4-star share",
      "min": 0,
      "max": 100,
      "step": 1,
      "unit": "%",
      "default": 10
    },
    {
      "type": "range",
      "id": "pct_3",
      "label": "3-star share",
      "min": 0,
      "max": 100,
      "step": 1,
      "unit": "%",
      "default": 2
    },
    {
      "type": "range",
      "id": "pct_2",
      "label": "2-star share",
      "min": 0,
      "max": 100,
      "step": 1,
      "unit": "%",
      "default": 1
    },
    {
      "type": "range",
      "id": "pct_1",
      "label": "1-star share",
      "min": 0,
      "max": 100,
      "step": 1,
      "unit": "%",
      "default": 1
    },
    {
      "type": "header",
      "content": "Review wall"
    },
    {
      "type": "select",
      "id": "rows",
      "label": "Rows",
      "info": "With two rows, reviews alternate between rows that drift in opposite directions.",
      "options": [
        { "value": "1", "label": "One row" },
        { "value": "2", "label": "Two rows" }
      ],
      "default": "2"
    },
    {
      "type": "select",
      "id": "speed",
      "label": "Scroll speed",
      "options": [
        { "value": "slow", "label": "Slow" },
        { "value": "medium", "label": "Medium" },
        { "value": "fast", "label": "Fast" }
      ],
      "default": "medium"
    },
    {
      "type": "text",
      "id": "verified_label",
      "label": "Verified badge text",
      "default": "Verified buyer"
    },
    {
      "type": "text",
      "id": "footnote",
      "label": "Trust note",
      "default": "Reviews are collected after delivery and published unedited."
    },
    {
      "type": "text",
      "id": "pause_label",
      "label": "Pause button label",
      "default": "Pause"
    },
    {
      "type": "header",
      "content": "Accessibility"
    },
    {
      "type": "text",
      "id": "list_label",
      "label": "Review list name",
      "info": "Announced by screen readers.",
      "default": "Customer reviews"
    },
    {
      "type": "text",
      "id": "rating_sr",
      "label": "Rating text for screen readers",
      "info": "[rating] is replaced by the score.",
      "default": "Rated [rating] out of 5"
    },
    {
      "type": "header",
      "content": "Layout"
    },
    {
      "type": "range",
      "id": "padding_top",
      "label": "Top padding",
      "min": 0,
      "max": 160,
      "step": 8,
      "unit": "px",
      "default": 88
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "label": "Bottom padding",
      "min": 0,
      "max": 160,
      "step": 8,
      "unit": "px",
      "default": 72
    },
    {
      "type": "header",
      "content": "Colors"
    },
    {
      "type": "color",
      "id": "color_bg",
      "label": "Background",
      "default": "#f2ede6"
    },
    {
      "type": "color",
      "id": "color_text",
      "label": "Text",
      "default": "#1c1814"
    },
    {
      "type": "color",
      "id": "color_card",
      "label": "Card background",
      "default": "#fffcf8"
    },
    {
      "type": "color",
      "id": "color_accent",
      "label": "Accent (stars, badges)",
      "default": "#b8742a"
    },
    {
      "type": "color",
      "id": "color_feature",
      "label": "Highlighted card background",
      "default": "#221c17"
    }
  ],
  "blocks": [
    {
      "type": "review",
      "name": "Review",
      "settings": [
        {
          "type": "range",
          "id": "rating",
          "label": "Rating",
          "min": 1,
          "max": 5,
          "step": 1,
          "default": 5
        },
        {
          "type": "text",
          "id": "title",
          "label": "Review title",
          "default": "My new daily staple"
        },
        {
          "type": "textarea",
          "id": "quote",
          "label": "Review",
          "default": "The texture is lovely and my skin feels calmer after two weeks. I have already reordered."
        },
        {
          "type": "text",
          "id": "author",
          "label": "Customer name",
          "default": "Alex M."
        },
        {
          "type": "text",
          "id": "detail",
          "label": "Detail",
          "info": "Product bought or location, e.g. Hydra Glow Serum · Lyon",
          "default": "Verified order"
        },
        {
          "type": "image_picker",
          "id": "image",
          "label": "Customer photo",
          "info": "Optional. Shown as a round avatar; leave empty for gradient initials."
        },
        {
          "type": "checkbox",
          "id": "verified",
          "label": "Verified buyer",
          "default": true
        },
        {
          "type": "checkbox",
          "id": "highlight",
          "label": "Highlight this review",
          "info": "Shows the card in the dark highlight style.",
          "default": false
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "EX · Review wall",
      "blocks": [
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "Glow by week two",
            "quote": "I was skeptical of another vitamin C serum, but my skin looks rested even after four hours of sleep. No sting, no pilling under sunscreen.",
            "author": "Camille R.",
            "detail": "Hydra Glow Serum · Lyon"
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "My barrier finally calmed down",
            "quote": "Two weeks of the night cream and the redness around my nose is gone. Rich without feeling heavy on the pillow.",
            "author": "Aisha Malik",
            "detail": "Night Repair Cream · London"
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 4,
            "title": "Lovely, wish it came bigger",
            "quote": "The mist is so fine it feels like stepping into fog. I go through a bottle a month, so a refill size would be perfect.",
            "author": "Jonas W.",
            "detail": "Barrier Mist · Berlin"
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "The SPF I actually wear",
            "quote": "No white cast on my deep skin and no shine by noon. Third bottle, and I have stopped looking for anything else.",
            "author": "Nadia Okafor",
            "detail": "Daily Shield SPF 50 · Brooklyn",
            "highlight": true
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "Smells like a cabin in October",
            "quote": "Fig and cedar without being sweet. It burns evenly to the edge and fills the whole living room.",
            "author": "Théo L.",
            "detail": "Ember Candle · Montréal"
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "Care that feels personal",
            "quote": "Support answered my question within the hour and my order arrived beautifully packed, completely plastic-free.",
            "author": "Sofia García",
            "detail": "Verified order · Madrid"
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "Silky, never oily",
            "quote": "It sinks in before I finish getting dressed. The neroli is soft enough to wear with perfume, and my legs have never looked better.",
            "author": "Mei Tanaka",
            "detail": "Velvet Body Oil · Singapore",
            "highlight": true
          }
        },
        {
          "type": "review",
          "settings": {
            "rating": 5,
            "title": "Morning puffiness, gone",
            "quote": "I keep it in the fridge and pat it on while the kettle boils. Ten minutes later I look like I actually slept.",
            "author": "Hannah K.",
            "detail": "Midnight Eye Balm · Oslo"
          }
        }
      ]
    }
  ]
}
{% endschema %}
Why it converts

Review volume, a visible score and verification cues near the buy decision answer the silent question: do people like me love this?

Specs

File: sections/mf-review-wall.liquidIn the theme editor: EX · Review wallBest on: index, product, page templatesSettings: 38 · blocks: ReviewValidated: Shopify Theme Check, no external assets

About this section

Review Wall is a free Shopify Online Store 2.0 section that turns customer reviews into a slow, editorial marquee. The header pairs an eyebrow, a heading with an italic serif accent, rich text and a button with a glassy scorecard: a large average score, stars with partial fill (4.8 fills 96% of five stars), a verdict word, a review count line such as Based on 2,340 verified reviews, and an optional 5-to-1 rating breakdown driven by five percentage settings. On product pages the score and count can come from the reviews.rating and reviews.rating_count metafields that most review apps write. Below, one or two rows of review cards drift in opposite directions behind soft faded edges. Each review block has a star rating, an optional title, the review text, the customer name, a detail line for the product bought or city, an optional photo (otherwise a gradient initials avatar), a Verified buyer badge and a Highlight option for a dark accent card. Rows pause on hover and keyboard focus, a Pause toggle stops the whole wall, and the animation also pauses off screen. When the section scrolls into view the score digits roll into place and the bars grow. The track is duplicated in Liquid for a seamless loop, with copies hidden from screen readers; with reduced motion, rows become static swipeable lists. The conversion principle is social proof: many specific, verified experiences reduce perceived risk right before purchase. It ships as one self-contained file with scoped CSS and JavaScript and no external assets.

Questions

Can the score come from my review app automatically?

Yes, on product pages. Keep Use the product's rating on product pages switched on and the section reads the reviews.rating and reviews.rating_count metafields that apps such as Judge.me, Okendo, Yotpo and Shopify Product Reviews write. On the home page or other pages it uses the Average rating and Number of reviews settings.

How do the two rows work?

Add review blocks in the theme editor. With Rows set to Two rows, reviews alternate between the top row, which drifts left, and the bottom row, which drifts right. Short lists are repeated automatically so the loop never shows a gap, and the speed stays the same per card whatever the number of reviews.

Is a moving marquee accessible?

Each review is announced once: duplicated copies are hidden from assistive technology and contain nothing focusable. Rows pause on hover and when focused with the keyboard, a visible Pause toggle stops the whole wall, and visitors who prefer reduced motion get static, horizontally scrollable rows instead of animation.

What if a customer has no photo?

Leave the Customer photo empty and the card shows a gradient avatar with the customer's initials, taken from the first and last word of their name. Add a photo and it replaces the initials in a round frame.

More sections