{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Before / after slider: two images in one frame, a draggable divider reveals the "before" image over the "after" image. A native range input drives the divider, so keyboard and screen readers work; pointer drag works anywhere on the frame. Horizontal or vertical split. Without JavaScript only the "after" image shows, in full. The divider moves along the physical x / y axis of the photo, so its CSS uses left / top on purpose. Depends on: snippets/lib-image.liquid, snippets/lib-button.liquid {%- endcomment -%} {%- comment -%} "Adapt" takes its shape from the after image; both images always share that frame. {%- endcomment -%} {%- liquid assign s = section.settings assign heading_tag = s.heading_tag | default: 'h2' assign eager = false if section.index <= 2 assign eager = true endif assign label_before = s.label_before | default: 'Before' assign label_after = s.label_after | default: 'After' assign ratio_d = '3 / 2' assign after_d = s.image_after if after_d == blank assign after_d = s.image_after_mobile endif if after_d != blank and after_d.width and after_d.height assign ratio_d = after_d.width | append: ' / ' | append: after_d.height endif assign ratio_m = ratio_d if s.image_after_mobile != blank and s.image_after_mobile.width and s.image_after_mobile.height assign ratio_m = s.image_after_mobile.width | append: ' / ' | append: s.image_after_mobile.height endif assign sizes = '(min-width: 1200px) 1200px, 100vw' if s.layout == 'beside' assign sizes = '(min-width: 990px) 60vw, 100vw' endif assign has_copy = false if s.heading != blank or s.text != blank or s.button_label != blank assign has_copy = true endif -%}
{%- if has_copy -%}
{%- if s.heading != blank -%} <{{ heading_tag }} class="lib-before-after__heading lib-before-after__heading--{{ s.heading_size }}"> {{- s.heading -}} {%- endif -%} {%- if s.text != blank -%}
{{ s.text }}
{%- endif -%} {%- if s.button_label != blank -%}
{%- render 'lib-button', label: s.button_label, url: s.button_url, style: s.button_style -%}
{%- endif -%}
{%- endif -%}
{%- render 'lib-image', image: s.image_after, image_mobile: s.image_after_mobile, class: 'lib-before-after__img', sizes: sizes, eager: eager, placeholder: 'lifestyle-2' -%} {%- if s.show_labels -%} {%- endif -%}
{%- render 'lib-image', image: s.image_before, image_mobile: s.image_before_mobile, class: 'lib-before-after__img', sizes: sizes, eager: eager, placeholder: 'lifestyle-1' -%} {%- if s.show_labels -%} {%- endif -%}
{% stylesheet %} .lib-before-after { 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-before-after__inner { max-width: 1200px; margin: 0 auto; display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); } .lib-before-after--full .lib-before-after__inner { max-width: none; } .lib-before-after__inner > * { min-width: 0; } @media (max-width: 749px) { .lib-before-after { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-before-after[data-show='desktop'] { display: none; } .lib-before-after[data-mobile-order='image_first'] .lib-before-after__media { order: -1; } } @media (min-width: 750px) { .lib-before-after[data-show='mobile'] { display: none; } } @media (min-width: 990px) { .lib-before-after--beside .lib-before-after__inner { grid-template-columns: 2fr 3fr; } .lib-before-after--beside[data-image-position='left'] .lib-before-after__inner { grid-template-columns: 3fr 2fr; } .lib-before-after--beside[data-image-position='left'] .lib-before-after__media { order: -1; } .lib-before-after--beside .lib-before-after__inner { align-items: center; } } /* Copy: alignment for desktop, then mobile override. */ .lib-before-after__copy { display: flex; flex-direction: column; align-items: flex-start; text-align: left; } .lib-before-after__copy[data-align='center'] { align-items: center; text-align: center; } .lib-before-after__copy[data-align='right'] { align-items: flex-end; text-align: right; } .lib-before-after--stacked .lib-before-after__copy[data-align='center'] { margin-inline: auto; } .lib-before-after--stacked .lib-before-after__copy[data-align='right'] { margin-inline-start: auto; } .lib-before-after--stacked .lib-before-after__copy { max-width: 44rem; } @media (max-width: 749px) { .lib-before-after__copy[data-align-m='left'] { align-items: flex-start; text-align: left; } .lib-before-after__copy[data-align-m='center'] { align-items: center; text-align: center; } .lib-before-after__copy[data-align-m='right'] { align-items: flex-end; text-align: right; } } .lib-before-after__heading { margin: 0; font-size: clamp(2rem, 4vw, 3.25rem); line-height: 1.08; letter-spacing: -0.025em; text-wrap: balance; } .lib-before-after__heading--small { font-size: clamp(1.5rem, 2.5vw, 2rem); } .lib-before-after__heading--medium { font-size: clamp(1.75rem, 3vw, 2.5rem); } .lib-before-after__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); } .lib-before-after__text { margin-top: 1rem; max-width: 34rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.78; } .lib-before-after__text > :first-child { margin-top: 0; } .lib-before-after__text > :last-child { margin-bottom: 0; } .lib-before-after__actions { margin-top: 1.75rem; } /* Compare frame: both images fill the same box, so they always share one shape. */ .lib-before-after__compare { --lib-ar-d: 3 / 2; --lib-ar-m: 4 / 5; position: relative; display: block; aspect-ratio: var(--lib-ar-d); overflow: hidden; border-radius: var(--lib-radius-img); background: color-mix(in srgb, var(--lib-text) 6%, transparent); user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; } .lib-before-after__compare[data-ratio='square'] { --lib-ar-d: 1 / 1; } .lib-before-after__compare[data-ratio='portrait'] { --lib-ar-d: 4 / 5; } .lib-before-after__compare[data-ratio='landscape'] { --lib-ar-d: 3 / 2; } .lib-before-after__compare[data-ratio='wide'] { --lib-ar-d: 16 / 9; } .lib-before-after__compare[data-ratio='adapt'] { --lib-ar-d: var(--lib-adapt-d); } .lib-before-after__compare[data-ratio-m='square'] { --lib-ar-m: 1 / 1; } .lib-before-after__compare[data-ratio-m='portrait'] { --lib-ar-m: 4 / 5; } .lib-before-after__compare[data-ratio-m='landscape'] { --lib-ar-m: 3 / 2; } .lib-before-after__compare[data-ratio-m='wide'] { --lib-ar-m: 16 / 9; } .lib-before-after__compare[data-ratio-m='adapt'] { --lib-ar-m: var(--lib-adapt-m); } @media (max-width: 749px) { .lib-before-after__compare { aspect-ratio: var(--lib-ar-m); } } .lib-before-after__compare:defined { cursor: ew-resize; touch-action: pan-y; } .lib-before-after__compare[data-orientation='vertical']:defined { cursor: ns-resize; touch-action: pan-x; } .lib-before-after__layer { position: absolute; inset: 0; } .lib-before-after__layer--before { clip-path: inset(0 calc(100% - var(--lib-pos)) 0 0); } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__layer--before { clip-path: inset(0 0 calc(100% - var(--lib-pos)) 0); } .lib-before-after__img { display: block; width: 100%; height: 100%; object-fit: cover; pointer-events: none; -webkit-user-drag: none; } .lib-before-after__img:is(svg) { background: color-mix(in srgb, var(--lib-text) 10%, transparent); fill: color-mix(in srgb, var(--lib-text) 25%, transparent); } .lib-before-after__layer--before .lib-before-after__img:is(svg) { background: color-mix(in srgb, var(--lib-text) 22%, transparent); } /* No JavaScript: show the after image only. */ .lib-before-after__compare:not(:defined) .lib-before-after__layer--before, .lib-before-after__compare:not(:defined) .lib-before-after__handle, .lib-before-after__compare:not(:defined) .lib-before-after__range { display: none; } /* Labels: each sits inside its own image layer, so the divider uncovers it naturally. */ .lib-before-after__label { position: absolute; top: clamp(0.75rem, 2vw, 1.25rem); padding: 0.4rem 0.8rem; border-radius: 999px; background: var(--lib-knob); color: var(--lib-knob-icon); font-size: 0.8rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.2; white-space: nowrap; } .lib-before-after__compare[data-label-pos='bottom'] .lib-before-after__label { top: auto; bottom: clamp(0.75rem, 2vw, 1.25rem); } .lib-before-after__label--before { left: clamp(0.75rem, 2vw, 1.25rem); } .lib-before-after__label--after { right: clamp(0.75rem, 2vw, 1.25rem); } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__label { left: clamp(0.75rem, 2vw, 1.25rem); right: auto; } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__label--before { top: clamp(0.75rem, 2vw, 1.25rem); bottom: auto; } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__label--after { top: auto; bottom: clamp(0.75rem, 2vw, 1.25rem); } /* The range input is the real control: invisible, focusable, driven by keys and assistive tech. */ .lib-before-after__range { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; pointer-events: none; } /* Divider line and knob. */ .lib-before-after__handle { position: absolute; top: 0; bottom: 0; left: var(--lib-pos); width: 2px; transform: translateX(-50%); background: var(--lib-line); box-shadow: 0 0 6px rgb(0 0 0 / 0.25); pointer-events: none; } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__handle { top: var(--lib-pos); bottom: auto; left: 0; right: 0; width: auto; height: 2px; transform: translateY(-50%); } .lib-before-after__knob { position: absolute; top: 50%; left: 50%; display: grid; place-items: center; width: 2.75rem; height: 2.75rem; border-radius: 50%; transform: translate(-50%, -50%); background: var(--lib-knob); color: var(--lib-knob-icon); box-shadow: 0 4px 14px rgb(0 0 0 / 0.25); transition: transform 0.2s ease; } .lib-before-after__compare[data-orientation='vertical'] .lib-before-after__knob svg { transform: rotate(90deg); } .lib-before-after__compare[data-knob='arrows'] .lib-before-after__knob { width: 3.25rem; background: none; box-shadow: none; color: var(--lib-line); filter: drop-shadow(0 1px 3px rgb(0 0 0 / 0.45)); } .lib-before-after__compare[data-knob='arrows'] .lib-before-after__knob svg { width: 2.25rem; height: 2.25rem; } .lib-before-after__compare[data-dragging] .lib-before-after__knob { transform: translate(-50%, -50%) scale(1.08); } .lib-before-after__range:focus-visible + .lib-before-after__handle { outline: 2px solid var(--lib-accent); outline-offset: 2px; } .lib-before-after__range:focus-visible + .lib-before-after__handle .lib-before-after__knob { outline: 3px solid var(--lib-accent); outline-offset: 3px; } /* After a mouse or touch drag the input keeps focus for the keys, but the ring stays hidden until a key is pressed. */ .lib-before-after__compare[data-pointer] .lib-before-after__range:focus-visible + .lib-before-after__handle, .lib-before-after__compare[data-pointer] .lib-before-after__range:focus-visible + .lib-before-after__handle .lib-before-after__knob { outline: none; } @media (prefers-reduced-motion: reduce) { .lib-before-after__knob { transition: none; } .lib-before-after__compare[data-dragging] .lib-before-after__knob { transform: translate(-50%, -50%); } } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-before-after-slider')) { customElements.define( 'lib-before-after-slider', class extends HTMLElement { connectedCallback() { if (this._ready) return; this.input = this.querySelector('.lib-before-after__range'); if (!this.input) return; this._ready = true; this.vertical = this.dataset.orientation === 'vertical'; this.onInput = () => this.set(Number(this.input.value)); this.onDown = (e) => { if (e.pointerType === 'mouse' && e.button !== 0) return; if (e.pointerType === 'mouse') e.preventDefault(); this.pointerId = e.pointerId; try { this.setPointerCapture(e.pointerId); } catch (err) {} this.setAttribute('data-dragging', ''); this.setAttribute('data-pointer', ''); this.input.focus({ preventScroll: true }); this.fromPointer(e); }; this.onMove = (e) => { if (e.pointerId === this.pointerId) this.fromPointer(e); }; this.onUp = (e) => { if (e.pointerId !== this.pointerId) return; this.pointerId = null; this.removeAttribute('data-dragging'); }; this.onKey = () => this.removeAttribute('data-pointer'); this.input.addEventListener('input', this.onInput); this.input.addEventListener('keydown', this.onKey); this.addEventListener('pointerdown', this.onDown); this.addEventListener('pointermove', this.onMove); this.addEventListener('pointerup', this.onUp); this.addEventListener('pointercancel', this.onUp); this.set(Number(this.input.value)); } disconnectedCallback() { if (!this._ready) return; this._ready = false; this.input.removeEventListener('input', this.onInput); this.input.removeEventListener('keydown', this.onKey); this.removeEventListener('pointerdown', this.onDown); this.removeEventListener('pointermove', this.onMove); this.removeEventListener('pointerup', this.onUp); this.removeEventListener('pointercancel', this.onUp); } fromPointer(e) { const r = this.getBoundingClientRect(); const size = this.vertical ? r.height : r.width; if (!size) return; const offset = this.vertical ? e.clientY - r.top : e.clientX - r.left; this.set((offset / size) * 100); } set(value) { const v = Math.min(100, Math.max(0, Number.isFinite(value) ? value : 50)); const rounded = Math.round(v); this.style.setProperty('--lib-pos', `${v}%`); if (Number(this.input.value) !== rounded) this.input.value = String(rounded); const before = this.dataset.labelBefore || 'Before'; const after = this.dataset.labelAfter || 'After'; this.input.setAttribute('aria-valuetext', `${before} ${rounded}%, ${after} ${100 - rounded}%`); } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Before / after", "tag": "section", "class": "lib-section", "settings": [ { "type": "header", "content": "Content" }, { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Ten minutes, years back" }, { "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": "large" }, { "type": "richtext", "id": "text", "label": "Text", "default": "

Drag the divider to see one bag before and after a single treatment with our cleaner and beeswax conditioner. Same light, same bench, nothing retouched but the leather.

" }, { "type": "header", "content": "Images" }, { "type": "paragraph", "content": "Use two photos taken from the same spot. Both are shown in the same frame, so they always share one shape." }, { "type": "image_picker", "id": "image_before", "label": "Before image (desktop)" }, { "type": "image_picker", "id": "image_before_mobile", "label": "Before image (mobile)", "info": "Optional. Shown below 750px." }, { "type": "image_picker", "id": "image_after", "label": "After image (desktop)", "info": "Shown on its own when JavaScript is off." }, { "type": "image_picker", "id": "image_after_mobile", "label": "After image (mobile)", "info": "Optional. Shown below 750px. Set both mobile images or neither." }, { "type": "select", "id": "image_ratio", "label": "Image shape on desktop", "info": "Adapt uses the shape of the after image.", "options": [ { "value": "adapt", "label": "Adapt to image" }, { "value": "square", "label": "Square" }, { "value": "portrait", "label": "Portrait" }, { "value": "landscape", "label": "Landscape" }, { "value": "wide", "label": "Wide" } ], "default": "landscape" }, { "type": "select", "id": "image_ratio_mobile", "label": "Image shape on mobile", "options": [ { "value": "adapt", "label": "Adapt to image" }, { "value": "square", "label": "Square" }, { "value": "portrait", "label": "Portrait" }, { "value": "landscape", "label": "Landscape" }, { "value": "wide", "label": "Wide" } ], "default": "portrait" }, { "type": "range", "id": "image_radius", "label": "Image corner radius", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 16 }, { "type": "header", "content": "Slider" }, { "type": "select", "id": "orientation", "label": "Divider direction", "options": [ { "value": "horizontal", "label": "Left to right" }, { "value": "vertical", "label": "Top to bottom" } ], "default": "horizontal" }, { "type": "range", "id": "start_position", "label": "Start position", "info": "How much of the before image shows at first.", "min": 0, "max": 100, "step": 5, "unit": "%", "default": 50 }, { "type": "select", "id": "knob_style", "label": "Handle style", "options": [ { "value": "circle", "label": "Circle with arrows" }, { "value": "arrows", "label": "Arrows only" }, { "value": "line", "label": "Line only" } ], "default": "circle" }, { "type": "checkbox", "id": "show_labels", "label": "Show labels on the images", "default": true }, { "type": "text", "id": "label_before", "label": "Before label", "default": "Before" }, { "type": "text", "id": "label_after", "label": "After label", "default": "After" }, { "type": "select", "id": "label_position", "label": "Label position", "visible_if": "{{ section.settings.show_labels and section.settings.orientation == 'horizontal' }}", "options": [ { "value": "top", "label": "Top" }, { "value": "bottom", "label": "Bottom" } ], "default": "top" }, { "type": "header", "content": "Button" }, { "type": "text", "id": "button_label", "label": "Button label", "default": "Shop the care kit" }, { "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": "primary" }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "layout", "label": "Layout", "options": [ { "value": "stacked", "label": "Text above, image full width" }, { "value": "beside", "label": "Image beside text" } ], "default": "beside" }, { "type": "select", "id": "image_position", "label": "Image position on desktop", "visible_if": "{{ section.settings.layout == 'beside' }}", "options": [ { "value": "right", "label": "Right" }, { "value": "left", "label": "Left" } ], "default": "right" }, { "type": "select", "id": "mobile_order", "label": "Order on mobile", "options": [ { "value": "text_first", "label": "Text first" }, { "value": "image_first", "label": "Image first" } ], "default": "text_first" }, { "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": "Text alignment on desktop", "default": "left" }, { "type": "select", "id": "text_alignment_mobile", "label": "Text 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": "#F6F1EA" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#2B211A" }, { "type": "color", "id": "color_accent", "label": "Accent (button, focus ring)", "default": "#8A4B22" }, { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" }, { "type": "color", "id": "color_knob", "label": "Divider, handle and label background", "default": "#FFFFFF" }, { "type": "color", "id": "color_knob_icon", "label": "Handle arrows and label text", "default": "#2B211A" }, { "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": 88 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 88 }, { "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": 56 }, { "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." } ], "presets": [ { "name": "Lib · Before / after" } ] } {% endschema %}