Shopify Before/After Slider Section

A drag-to-compare image slider for transformations, renovations or product results.

Describe the result you need. AI will set up the tool and ask for any missing details.

Configure with AI

Enter to submit · Shift + Enter for a new line

Sent to AI. Leave out passwords and private customer data.

Your details

Images

Right-click an image already on your store and choose Copy image address, or paste a URL from Content → Files.

Behavior
%
Style

Your section file (sections/before-after-slider.liquid)

before-after-slider.liquid
{%- comment -%}
  Before/after image comparison slider. Drag the handle, or focus it and use the arrow keys.
{%- endcomment -%}
{%- style -%}
  .ea-bas{position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;border-radius:8px;touch-action:none;user-select:none}
  .ea-bas img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
  .ea-bas__after{clip-path:inset(0 0 0 var(--ea-bas-pos))}
  .ea-bas__handle{position:absolute;top:0;bottom:0;left:var(--ea-bas-pos);width:3px;transform:translateX(-1.5px);background:#ffffff;cursor:ew-resize}
  .ea-bas__handle::after{content:'';position:absolute;top:50%;left:50%;width:2.2rem;height:2.2rem;border-radius:50%;background:#ffffff;transform:translate(-50%,-50%);box-shadow:0 1px 4px rgba(0,0,0,.3)}
  .ea-bas__label{position:absolute;padding:.25em .6em;background:rgba(0,0,0,.6);color:#fff;font-size:.75em;border-radius:4px;top:.6rem}
  .ea-bas__label--before{left:.6rem}
  .ea-bas__label--after{right:.6rem}
{%- endstyle -%}

<ea-before-after
  class="ea-bas"
  data-orientation="{{ section.settings.orientation }}"
  data-start="{{ section.settings.start_position }}"
  role="slider"
  tabindex="0"
  aria-label="Before and after image comparison"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuenow="{{ section.settings.start_position }}"
  style="--ea-bas-pos: {{ section.settings.start_position }}%;"
>
  <img src="{{ section.settings.before_image | image_url: width: 1200 }}" width="{{ section.settings.before_image.width }}" height="{{ section.settings.before_image.height }}" alt="{{ section.settings.before_label | escape }}">
  <img class="ea-bas__after" src="{{ section.settings.after_image | image_url: width: 1200 }}" width="{{ section.settings.after_image.width }}" height="{{ section.settings.after_image.height }}" alt="{{ section.settings.after_label | escape }}">
  <div class="ea-bas__handle"></div>
  {%- if section.settings.before_label != blank -%}<span class="ea-bas__label ea-bas__label--before">{{ section.settings.before_label | escape }}</span>{%- endif -%}
  {%- if section.settings.after_label != blank -%}<span class="ea-bas__label ea-bas__label--after">{{ section.settings.after_label | escape }}</span>{%- endif -%}
</ea-before-after>

<script>
  if (!customElements.get('ea-before-after')) {
    customElements.define('ea-before-after', class extends HTMLElement {
      connectedCallback() {
        this.axis = this.dataset.orientation === 'vertical' ? 'y' : 'x';
        this.set(Number(this.dataset.start) || 50);
        var self = this;
        var dragging = false;
        this.addEventListener('pointerdown', function (e) { dragging = true; self.setPointerCapture(e.pointerId); self.move(e); });
        this.addEventListener('pointermove', function (e) { if (dragging) self.move(e); });
        this.addEventListener('pointerup', function () { dragging = false; });
        this.addEventListener('keydown', function (e) {
          var current = self.current || 50;
          if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') self.set(current - 2);
          if (e.key === 'ArrowRight' || e.key === 'ArrowUp') self.set(current + 2);
        });
      }
      move(e) {
        var rect = this.getBoundingClientRect();
        var pct = this.axis === 'x' ? ((e.clientX - rect.left) / rect.width) * 100 : ((e.clientY - rect.top) / rect.height) * 100;
        this.set(pct);
      }
      set(pct) {
        pct = Math.max(0, Math.min(100, pct));
        this.current = pct;
        this.style.setProperty('--ea-bas-pos', pct + '%');
        this.setAttribute('aria-valuenow', Math.round(pct));
      }
    });
  }
</script>

{% schema %}
{
  "name": "Before / after slider",
  "tag": "section",
  "class": "ea-before-after-section",
  "settings": [
    {
      "type": "image_picker",
      "id": "before_image",
      "label": "Before image"
    },
    {
      "type": "image_picker",
      "id": "after_image",
      "label": "After image"
    },
    {
      "type": "text",
      "id": "before_label",
      "label": "Before label",
      "default": "Before"
    },
    {
      "type": "text",
      "id": "after_label",
      "label": "After label",
      "default": "After"
    },
    {
      "type": "range",
      "id": "start_position",
      "label": "Starting position",
      "min": 10,
      "max": 90,
      "step": 5,
      "unit": "%",
      "default": 50
    },
    {
      "type": "select",
      "id": "orientation",
      "label": "Direction",
      "options": [
        {
          "value": "horizontal",
          "label": "Left / right"
        },
        {
          "value": "vertical",
          "label": "Up / down"
        }
      ],
      "default": "horizontal"
    },
    {
      "type": "color",
      "id": "handle_color",
      "label": "Handle color",
      "default": "#ffffff"
    }
  ],
  "presets": [
    {
      "name": "Before / after slider"
    }
  ]
}
{% endschema %}

To install: in Shopify admin go to Online Store → Themes, click … next to your theme → Edit code. Under Sections choose Add a new section, name it as shown above, replace the contents with this code and save. Then open Customize and add the section where you want it. Try it on a duplicate of your theme first. It uses Shopify's image_picker settings, so upload your before and after photos directly in the theme editor rather than pasting URLs into the installed section.

The URLs you entered here are only used to build the live preview on this page; the installed section uses its own image_picker settings instead.

Live preview

CSS clip-path, no slider library

Two images sit on top of each other; a CSS clip-path: inset(...) on the top image hides part of it based on a --ea-bas-pos custom property, which a small custom element updates as you drag, tap, or use the arrow keys on the handle. No slider library or extra JavaScript dependency is loaded.

It works with pointer, touch and keyboard input (the handle is a focusable, role="slider" element), and both a left/right and an up/down orientation are available.

Adding it to your theme

  1. 01

    Add the section

    Online Store → Themes → Edit code → Sections → Add a new section named before-after-slider, paste the code, then add it in Customize and upload your before and after photos directly through the theme editor's image pickers.

Questions people ask

Why do I need to re-upload images in the theme editor if I already entered URLs here?

The URLs here only drive this page's live preview. The installed section uses Shopify's native image_picker settings so the images are properly optimized and resized by Shopify's CDN, which a pasted URL wouldn't be.

Does it work on mobile?

Yes, it responds to touch and pointer events the same way, and the aspect ratio keeps it from overflowing a narrow screen.

Can I add more than one comparison on a page?

Yes, add the section again; each instance has its own settings and images.

Will this affect page speed?

It loads two images and a small script, similar cost to any other image section. Use reasonably sized source photos for the best load time.

Want this builtinto your store?

I'm Emmanuel, a Shopify and WooCommerce developer. If this tool pointed at something you'd rather have fixed for you (a faster store, cleaner product data, a migration, a custom feature), send me a note.

Emmanuel Asika

Emmanuel Asika

Shopify & WooCommerce developer, Dublin

I build and fix online stores: new builds, migrations, speed, SEO and the custom bits apps can't do.

hello@emmanuelasika.com

Last reviewed 30 September 2026.