// Before/after slider: the range input is the control. This mirrors its value into --v
// (which moves the divider and clip) and gives screen readers a readable value.
document.querySelectorAll('[data-compare]').forEach((root) => {
const range = root.querySelector('[data-compare-range]');
if (!range) return;
const sync = () => {
root.style.setProperty('--v', range.value);
range.setAttribute('aria-valuetext', `${100 - range.value}% after`);
};
range.addEventListener('input', sync);
sync();
});
Bootstrap 5 code
Requires: Bootstrap 5.3.8 CSS
HTML
<div><span class="lbl">Renovation before and after cards</span>
<section aria-labelledby="rnb-title">
<div class="d-flex flex-wrap align-items-end justify-content-between column-gap-4 row-gap-1 mb-4"><div><p class="rn-kicker mb-0">Oakline Interiors</p><h2 id="rnb-title" class="rn-title fw-bold mt-1 mb-0">Recent renovations</h2></div><p class="rn-note mb-0">Drag each photo to compare</p></div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 g-4">
<div class="col"><article class="card rn-card h-100 border-0">
<div class="ic ratio ratio-4x3" data-compare>
<img src="https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" loading="lazy" alt="After: a white kitchen with black pendant lights, steel appliances and bar stools at an island">
<div class="ic-before"><img src="https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" class="w-100 h-100" loading="lazy" alt="Before: the same kitchen, dim and yellowed"></div>
<div class="pe-none"><span class="badge rounded-pill position-absolute ic-tag-b" aria-hidden="true">Before</span><span class="badge rounded-pill position-absolute ic-tag-a" aria-hidden="true">After</span></div>
<input class="form-range" type="range" min="0" max="100" value="50" aria-label="Compare the kitchen before and after" aria-valuetext="50% after" data-compare-range>
<span class="ic-handle" aria-hidden="true"><span class="ic-knob"><svg viewBox="0 0 24 24"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
</div>
<div class="card-body rn-body"><h3 class="card-title">Galley kitchen refit</h3><p class="rn-where mb-0">Hawthorn Street, Portland</p>
<dl class="row g-2 rn-stats mb-0"><div class="col-6"><dt>Budget</dt><dd class="mb-0">$24,800</dd></div><div class="col-6"><dt>Time</dt><dd class="mb-0">5 weeks</dd></div></dl></div>
</article></div>
<div class="col"><article class="card rn-card h-100 border-0">
<div class="ic ratio ratio-4x3" data-compare>
<img src="https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" loading="lazy" alt="After: a white bathroom with a walk-in glass shower and a long vanity">
<div class="ic-before"><img src="https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" class="w-100 h-100" loading="lazy" alt="Before: the same bathroom, dingy and discoloured"></div>
<div class="pe-none"><span class="badge rounded-pill position-absolute ic-tag-b" aria-hidden="true">Before</span><span class="badge rounded-pill position-absolute ic-tag-a" aria-hidden="true">After</span></div>
<input class="form-range" type="range" min="0" max="100" value="50" aria-label="Compare the bathroom before and after" aria-valuetext="50% after" data-compare-range>
<span class="ic-handle" aria-hidden="true"><span class="ic-knob"><svg viewBox="0 0 24 24"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
</div>
<div class="card-body rn-body"><h3 class="card-title">Walk-in shower bathroom</h3><p class="rn-where mb-0">Alder Court, Beaverton</p>
<dl class="row g-2 rn-stats mb-0"><div class="col-6"><dt>Budget</dt><dd class="mb-0">$13,500</dd></div><div class="col-6"><dt>Time</dt><dd class="mb-0">3 weeks</dd></div></dl></div>
</article></div>
<div class="col"><article class="card rn-card h-100 border-0">
<div class="ic ratio ratio-4x3" data-compare>
<img src="https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" loading="lazy" alt="After: a living room with a mustard armchair, a brass floor lamp and a framed print on a pale wall">
<div class="ic-before"><img src="https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=640&h=480&q=70" srcset="https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=400&h=300&q=70 400w, https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=640&h=480&q=70 640w, https://images.unsplash.com/photo-1586023492125-27b2c045efd7?auto=format&fit=crop&w=880&h=660&q=70 880w" sizes="(min-width: 992px) 345px, (min-width: 576px) 50vw, 100vw" width="880" height="660" class="w-100 h-100" loading="lazy" alt="Before: the same living room, faded and brownish"></div>
<div class="pe-none"><span class="badge rounded-pill position-absolute ic-tag-b" aria-hidden="true">Before</span><span class="badge rounded-pill position-absolute ic-tag-a" aria-hidden="true">After</span></div>
<input class="form-range" type="range" min="0" max="100" value="50" aria-label="Compare the living room before and after" aria-valuetext="50% after" data-compare-range>
<span class="ic-handle" aria-hidden="true"><span class="ic-knob"><svg viewBox="0 0 24 24"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
</div>
<div class="card-body rn-body"><h3 class="card-title">Reading corner makeover</h3><p class="rn-where mb-0">Larch Avenue, Lake Oswego</p>
<dl class="row g-2 rn-stats mb-0"><div class="col-6"><dt>Budget</dt><dd class="mb-0">$6,900</dd></div><div class="col-6"><dt>Time</dt><dd class="mb-0">9 days</dd></div></dl></div>
</article></div>
</div>
</section></div>
<div><span class="lbl">Product retouch showcase</span>
<article class="card pr text-bg-dark border-0" aria-labelledby="prb-title">
<div class="row g-0">
<div class="col-md-6"><div class="ic ratio ratio-1x1" data-compare>
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=720&h=720&q=70" srcset="https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=480&h=480&q=70 480w, https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=720&h=720&q=70 720w, https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=1000&h=1000&q=70 1000w" sizes="(min-width: 1120px) 540px, (min-width: 768px) 50vw, 100vw" width="1000" height="1000" loading="lazy" alt="Retouched: a red knit running shoe on a red background, with clean, vivid colour">
<div class="ic-before"><img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=720&h=720&q=70" srcset="https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=480&h=480&q=70 480w, https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=720&h=720&q=70 720w, https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=1000&h=1000&q=70 1000w" sizes="(min-width: 1120px) 540px, (min-width: 768px) 50vw, 100vw" width="1000" height="1000" class="w-100 h-100" loading="lazy" alt="Raw: the same shoe shot, muddy and underexposed"></div>
<div class="pe-none"><span class="badge rounded-pill position-absolute ic-tag-b" aria-hidden="true">Raw</span><span class="badge rounded-pill position-absolute ic-tag-a" aria-hidden="true">Retouched</span></div>
<input class="form-range" type="range" min="0" max="100" value="50" aria-label="Compare the raw and retouched shoe photo" aria-valuetext="50% after" data-compare-range>
<span class="ic-handle" aria-hidden="true"><span class="ic-knob"><svg viewBox="0 0 24 24"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
</div></div>
<div class="col-md-6 d-flex"><div class="card-body pr-body d-flex flex-column justify-content-center">
<p class="pr-kicker mb-0">Pixelcraft Studio</p>
<h3 id="prb-title" class="pr-title fw-bold mb-0">E-commerce product retouching</h3>
<p class="pr-lead mb-0">Colour matched to the real product, so returns for “it looked different online” go down.</p>
<ul class="pr-list list-unstyled mb-0"><li>Colour match against a physical sample</li><li>Dust, lint and crease clean-up</li><li>Background extension to any ratio</li></ul>
<div class="pr-price d-flex flex-wrap align-items-center justify-content-between gap-3"><div><small class="d-block">From</small><strong>$4.50 per image</strong></div><a class="btn btn-light rounded-pill" href="#pricing">See pricing</a></div>
</div></div>
</div>
</article></div>
// Before/after slider: the range input is the control. This mirrors its value into --v
// (which moves the divider and clip) and gives screen readers a readable value.
document.querySelectorAll('[data-compare]').forEach((root) => {
const range = root.querySelector('[data-compare-range]');
if (!range) return;
const sync = () => {
root.style.setProperty('--v', range.value);
range.setAttribute('aria-valuetext', `${100 - range.value}% after`);
};
range.addEventListener('input', sync);
sync();
});
/* The range value (0–100) is mirrored into --v; --x is the divider position, kept centred on the native thumb. */
@utility compare { --x: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--v) / 100); }
@utility clip-before { clip-path: inset(0 calc(100% - var(--x)) 0 0); }
@utility compare-range {
-webkit-appearance: none; appearance: none; background: none;
&::-webkit-slider-runnable-track { height: 100%; }
&::-webkit-slider-thumb { -webkit-appearance: none; width: var(--thumb); height: 100%; }
&::-moz-range-thumb { width: var(--thumb); height: 100%; border: 0; }
}
JavaScript
// Before/after slider: the range input is the control. This mirrors its value into --v
// (which moves the divider and clip) and gives screen readers a readable value.
document.querySelectorAll('[data-compare]').forEach((root) => {
const range = root.querySelector('[data-compare-range]');
if (!range) return;
const sync = () => {
root.style.setProperty('--v', range.value);
range.setAttribute('aria-valuetext', `${100 - range.value}% after`);
};
range.addEventListener('input', sync);
sync();
});
About this set
Before and after sliders placed inside real page sections. Renovation before and after cards is an interior design studio’s recent work: three cards in a grid, one, two or three columns wide, each with a 4:3 comparison slider of a kitchen, a bathroom or a living room, a title, the street and town, and a budget and time line. Every photo is lazy-loaded and has srcset, sizes, width and height, so a long grid of projects stays light and does not shift while it loads. Product retouch showcase is a dark split card for a retouching service: a square comparison of a running shoe, raw on the left and retouched on the right, beside a short pitch, three bullet points, a price from line and a See pricing link. Use the cards for builders, landscapers, cleaners, stagers and photographers, and the split card for any service that sells a visible result. Each slider is the same native range input technique as the classic set, so every card can be dragged, swiped or moved with the arrow keys, and each has its own label naming the room or product.
What’s included
Grid of renovation cards with a comparison slider in each
Lazy-loaded photos with srcset, sizes, width and height
Budget and time details in a description list
Dark product retouching card with a pitch, bullets and a price line
Bootstrap version built from .card, .row-cols-* and .ratio
Accessibility
Each slider is a range input labelled with the room or product, for example Compare the kitchen before and after, with an aria-valuetext such as 50% after. The grid is a list of article elements with headings, and the budget and time are a description list. Handles and the See pricing link show a visible focus outline, and the label fade is switched off under prefers-reduced-motion.
Customise it
Change the --ic-before filter for the before look, --thumb for the hit area and the card colours in .rn-card and .pr. In Bootstrap edit .row-cols-sm-2 and .row-cols-lg-3 and the .ratio-4x3 stage; in Tailwind edit the grid-cols utilities and the filter classes on the before images.