Open demo

CSS code

HTML
<div><span class="lbl">Photo editor with view modes</span>
<section class="ed" data-view="split" aria-labelledby="ed-title">
  <div class="ed-bar">
    <div class="ed-file"><h3 id="ed-title">Neon Alley</h3><span>DSC_0418.ARW · 6000 × 4000</span></div>
    <button class="ed-export" type="button">Export</button>
  </div>
  <div class="ed-main">
    <div class="ic" data-compare>
      <img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" loading="lazy" alt="Edit: a Tokyo shopping street lit by bright pink, blue and teal neon signs at dusk, with people crossing">
      <div class="ic-before"><img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" loading="lazy" alt="Original: the same street as a flat, washed-out camera file"></div>
      <span class="ic-tag ic-tag-b" aria-hidden="true">ORIGINAL</span><span class="ic-tag ic-tag-a" aria-hidden="true">EDIT</span>
      <input class="ic-range" type="range" min="0" max="100" value="50" aria-label="Compare the original file and the edit" 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.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
    </div>
    <aside class="ed-side" aria-label="Adjustments">
      <h4>Adjustments</h4>
      <dl class="ed-adj">
        <div><dt>Exposure</dt><dd>+0.35</dd><i style="--a:35" aria-hidden="true"></i></div>
        <div><dt>Contrast</dt><dd>+42</dd><i style="--a:42" aria-hidden="true"></i></div>
        <div><dt>Highlights</dt><dd>−38</dd><i class="neg" style="--a:38" aria-hidden="true"></i></div>
        <div><dt>Shadows</dt><dd>+24</dd><i style="--a:24" aria-hidden="true"></i></div>
        <div><dt>Vibrance</dt><dd>+56</dd><i style="--a:56" aria-hidden="true"></i></div>
        <div><dt>Clarity</dt><dd>+18</dd><i style="--a:18" aria-hidden="true"></i></div>
      </dl>
    </aside>
  </div>
  <div class="ed-foot">
    <div class="ed-views" role="group" aria-label="View">
      <button type="button" aria-pressed="true" data-view-btn="split">Split</button>
      <button type="button" aria-pressed="false" data-view-btn="side">Side by side</button>
      <button type="button" aria-pressed="false" data-view-btn="edit">Edit only</button>
    </div>
    <span>Fit · 18%</span>
  </div>
</section></div>

<div><span class="lbl">Compact card with a visible slider</span>
<article class="cr" aria-labelledby="cr-title">
  <div class="ic" data-compare data-after-left>
    <img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" loading="lazy" alt="Before: a starry sky over a cone-shaped volcano and a lake, underexposed and murky">
    <div class="ic-before"><img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" loading="lazy" alt="After: the same night sky full of stars over the volcano, reflected in a calm blue lake"></div>
    <span class="ic-tag ic-tag-b" aria-hidden="true">FINAL</span><span class="ic-tag ic-tag-a" aria-hidden="true">RAW</span>
    <span class="ic-handle" aria-hidden="true"></span>
  </div>
  <div class="cr-row">
    <label for="cr-range">Final</label>
    <input class="cr-range" id="cr-range" type="range" min="0" max="100" value="50" aria-valuetext="50% after" data-compare-range>
    <output for="cr-range" aria-hidden="true">50%</output>
  </div>
  <h3 id="cr-title">Fuji from Lake Motosu</h3>
  <p>30 s · f/2.8 · ISO 3200 · stacked ×8</p>
</article></div>
CSS
/* Compare stage: the range value (0–100) drives --v, which places the divider and clips the original. */
.ic{--v:50;--thumb:44px;--x:calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--v) / 100);
  position:relative;display:block;aspect-ratio:3/2;overflow:hidden;background:#07080b;isolation:isolate;user-select:none;-webkit-user-select:none}
.ic img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
.ic-before{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--x)) 0 0)}
.ic-before img{filter:var(--ic-before,brightness(1.08) contrast(.68) saturate(.4))}
.ic-tag{position:absolute;top:12px;padding:4px 9px;border-radius:6px;background:rgba(7,8,11,.82);color:#e8e9ee;font:600 .7rem/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;pointer-events:none;transition:opacity .2s}
.ic-tag-b{left:12px;opacity:clamp(0,calc((var(--v) - 14) / 8),1)}
.ic-tag-a{right:12px;opacity:clamp(0,calc((86 - var(--v)) / 8),1)}
.ic-range{position:absolute;inset:0;z-index:2;width:100%;height:100%;margin:0;opacity:0;cursor:ew-resize;-webkit-appearance:none;appearance:none;background:none}
.ic-range::-webkit-slider-runnable-track{height:100%}
.ic-range::-webkit-slider-thumb{-webkit-appearance:none;width:var(--thumb);height:100%}
.ic-range::-moz-range-thumb{width:var(--thumb);height:100%;border:0}
.ic-handle{position:absolute;top:0;bottom:0;left:var(--x);width:0;pointer-events:none;z-index:1}
.ic-handle::before{content:"";position:absolute;top:0;bottom:0;left:-1px;width:2px;background:#8aa8ff;box-shadow:0 0 12px rgba(138,168,255,.6)}
.ic-knob{position:absolute;top:50%;left:0;display:grid;place-items:center;width:30px;height:52px;translate:-50% -50%;border-radius:8px;background:#8aa8ff;color:#0b0c10;box-shadow:0 4px 16px rgba(0,0,0,.5)}
.ic-knob svg{width:16px;height:16px}
.ic-range:focus-visible ~ .ic-handle .ic-knob{outline:3px solid #fff;outline-offset:3px}

/* 1. Editor frame */
.ed{overflow:hidden;border:1px solid #262a33;border-radius:16px;background:#14161c;box-shadow:0 30px 60px -30px rgba(0,0,0,.8)}
.ed-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 18px;padding:12px 16px;border-bottom:1px solid #262a33}
.ed-file{display:flex;align-items:center;gap:10px;min-width:0}
.ed-file h3{font-size:.95rem;font-weight:600}
.ed-file span{font:500 .78rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#9aa0ad}
.ed-export{padding:8px 14px;border:0;border-radius:8px;background:#8aa8ff;color:#0b0c10;font:inherit;font-size:.84rem;font-weight:700;cursor:pointer}
.ed-export:hover{background:#a6bdff}
.ed-main{display:grid}
@media (min-width:820px){.ed-main{grid-template-columns:1fr 250px}}
.ed-side{padding:16px 18px;border-top:1px solid #262a33}
@media (min-width:820px){.ed-side{border-top:0;border-left:1px solid #262a33}}
.ed-side h4{margin-bottom:12px;font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#9aa0ad}
.ed-adj{display:grid;gap:12px;font-size:.84rem}
.ed-adj div{display:grid;grid-template-columns:1fr auto;gap:6px}
.ed-adj dt{color:#c3c7d1}
.ed-adj dd{font:600 .8rem/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:#e8e9ee}
.ed-adj i{grid-column:1/-1;position:relative;height:4px;border-radius:2px;background:#262a33}
.ed-adj i::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:calc(var(--a) * .5%);border-radius:2px;background:#8aa8ff}
.ed-adj i.neg::after{left:auto;right:50%}
.ed-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;padding:10px 16px;border-top:1px solid #262a33;font-size:.8rem;color:#9aa0ad}
.ed-views{display:inline-flex;gap:2px;padding:3px;border-radius:9px;background:#0b0c10}
.ed-views button{padding:6px 12px;border:0;border-radius:7px;background:none;color:#c3c7d1;font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}
.ed-views button[aria-pressed="true"]{background:#262a33;color:#fff}
.ed-views button:focus-visible,.ed-export:focus-visible{outline:2px solid #8aa8ff;outline-offset:2px}
/* View modes set by the buttons: side by side shows both photos whole, edit only hides the original */
.ed[data-view="side"] .ic-before{clip-path:inset(0 50% 0 0)}
.ed[data-view="side"] .ic-before img{width:50%}
.ed[data-view="side"] .ic > img{left:50%;width:50%}
.ed[data-view="side"] .ic-handle{left:50%}
.ed[data-view="side"] .ic-knob,.ed[data-view="edit"] .ic-handle,.ed[data-view="edit"] .ic-before,.ed[data-view="edit"] .ic-tag-b{display:none}
.ed[data-view="side"] .ic-tag{opacity:1}
.ed:not([data-view="split"]) .ic-range{display:none}

/* 2. Compact card with a visible range under the photo and a live readout */
.cr{max-width:520px;padding:14px;border:1px solid #262a33;border-radius:18px;background:#14161c}
.cr .ic{border-radius:10px;aspect-ratio:16/10}
/* here the clipped left layer is the final photo and the base is the raw file, so the value is the share of "after" */
.cr .ic > img{filter:brightness(.6) contrast(.85) saturate(.5)}
.cr .ic-before img{filter:none}
.cr-row{display:flex;align-items:center;gap:14px;padding:16px 4px 4px}
.cr-row label{font-size:.84rem;font-weight:600;white-space:nowrap}
.cr-range{flex:1;min-width:0;height:24px;-webkit-appearance:none;appearance:none;background:none;cursor:pointer}
.cr-range::-webkit-slider-runnable-track{height:6px;border-radius:3px;background:linear-gradient(90deg,#8aa8ff var(--fill,50%),#3a3f4b 0)}
.cr-range::-moz-range-track{height:6px;border-radius:3px;background:#3a3f4b}
.cr-range::-moz-range-progress{height:6px;border-radius:3px;background:#8aa8ff}
.cr-range::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;margin-top:-7px;border-radius:50%;background:#fff;border:3px solid #8aa8ff}
.cr-range::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:#fff;border:3px solid #8aa8ff}
.cr-range:focus-visible{outline:2px solid #8aa8ff;outline-offset:4px;border-radius:4px}
.cr output{min-width:4.5ch;font:600 .82rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#c3c7d1;text-align:right}
.cr h3{padding:2px 4px 0;font-size:.95rem;font-weight:600}
.cr p{padding:2px 4px 0;font-size:.82rem;color:#9aa0ad}
@media (prefers-reduced-motion:reduce){.ic-tag{transition:none}}
JavaScript
// Compare: the range input is the control. Its value goes to --v on the stage; a range
// outside the stage (the compact card) finds its stage in the same card.
document.querySelectorAll('[data-compare-range]').forEach((range) => {
  const card = range.closest('[data-compare]') || range.closest('article, section');
  const stage = card.matches('[data-compare]') ? card : card.querySelector('[data-compare]');
  const out = card.querySelector('output');
  const sync = () => {
    stage.style.setProperty('--v', range.value);
    range.style.setProperty('--fill', `${range.value}%`);
    const after = stage.hasAttribute('data-after-left') ? range.value : 100 - range.value;
    range.setAttribute('aria-valuetext', `${after}% after`);
    if (out) out.textContent = `${range.value}%`;
  };
  range.addEventListener('input', sync);
  sync();
});
// Editor view modes: one pressed button at a time; data-view on the editor switches the layout.
document.querySelectorAll('[data-view]').forEach((ed) => {
  const btns = [...ed.querySelectorAll('[data-view-btn]')];
  btns.forEach((btn) => btn.addEventListener('click', () => {
    ed.dataset.view = btn.dataset.viewBtn;
    btns.forEach((b) => b.setAttribute('aria-pressed', b === btn));
  }));
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Photo editor with view modes</span>
<section class="card ed" data-bs-theme="dark" data-view="split" aria-labelledby="edb-title">
  <div class="card-header d-flex flex-wrap align-items-center justify-content-between column-gap-3 row-gap-2">
    <div class="d-flex align-items-center gap-2 text-truncate"><h3 id="edb-title" class="ed-title fw-semibold m-0">Neon Alley</h3><span class="ed-meta text-body-secondary">DSC_0418.ARW · 6000 × 4000</span></div>
    <button class="btn btn-primary" type="button">Export</button>
  </div>
  <div class="ed-main d-grid">
    <div class="ic ratio" data-compare>
      <img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" loading="lazy" alt="Edit: a Tokyo shopping street lit by bright pink, blue and teal neon signs at dusk, with people crossing">
      <div class="ic-before"><img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" class="w-100 h-100" loading="lazy" alt="Original: the same street as a flat, washed-out camera file"></div>
      <div class="pe-none"><span class="ic-tag ic-tag-b position-absolute" aria-hidden="true">ORIGINAL</span><span class="ic-tag ic-tag-a position-absolute" aria-hidden="true">EDIT</span></div>
      <input class="form-range" type="range" min="0" max="100" value="50" aria-label="Compare the original file and the edit" 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.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
    </div>
    <aside class="ed-side border-top" aria-label="Adjustments">
      <h4>Adjustments</h4>
      <dl class="ed-adj">
        <div class="ed-adj-row"><dt>Exposure</dt><dd>+0.35</dd><i class="" style="--a:35" aria-hidden="true"></i></div>
        <div class="ed-adj-row"><dt>Contrast</dt><dd>+42</dd><i class="" style="--a:42" aria-hidden="true"></i></div>
        <div class="ed-adj-row"><dt>Highlights</dt><dd>−38</dd><i class="neg" style="--a:38" aria-hidden="true"></i></div>
        <div class="ed-adj-row"><dt>Shadows</dt><dd>+24</dd><i class="" style="--a:24" aria-hidden="true"></i></div>
        <div class="ed-adj-row"><dt>Vibrance</dt><dd>+56</dd><i class="" style="--a:56" aria-hidden="true"></i></div>
        <div class="ed-adj-row"><dt>Clarity</dt><dd>+18</dd><i class="" style="--a:18" aria-hidden="true"></i></div>
        </dl>
    </aside>
  </div>
  <div class="card-footer d-flex flex-wrap align-items-center justify-content-between gap-2 text-body-secondary">
    <div class="btn-group ed-views" role="group" aria-label="View">
      <button class="btn" type="button" aria-pressed="true" data-view-btn="split">Split</button>
      <button class="btn" type="button" aria-pressed="false" data-view-btn="side">Side by side</button>
      <button class="btn" type="button" aria-pressed="false" data-view-btn="edit">Edit only</button>
    </div>
    <span>Fit · 18%</span>
  </div>
</section></div>

<div><span class="lbl">Compact card with a visible slider</span>
<article class="card cr" data-bs-theme="dark" aria-labelledby="crb-title">
  <div class="ic ratio" data-compare data-after-left>
    <img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" loading="lazy" alt="Before: a starry sky over a cone-shaped volcano and a lake, underexposed and murky">
    <div class="ic-before"><img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" class="w-100 h-100" loading="lazy" alt="After: the same night sky full of stars over the volcano, reflected in a calm blue lake"></div>
    <div class="pe-none"><span class="ic-tag ic-tag-b position-absolute" aria-hidden="true">FINAL</span><span class="ic-tag ic-tag-a position-absolute" aria-hidden="true">RAW</span></div>
    <span class="ic-handle" aria-hidden="true"></span>
  </div>
  <div class="cr-row d-flex align-items-center gap-3 px-1 pt-3 pb-1">
    <label for="crb-range" class="text-nowrap">Final</label>
    <input class="form-range flex-grow-1" id="crb-range" type="range" min="0" max="100" value="50" aria-valuetext="50% after" data-compare-range>
    <output for="crb-range" aria-hidden="true">50%</output>
  </div>
  <h3 id="crb-title" class="cr-title fw-semibold mb-0 px-1 pt-1">Fuji from Lake Motosu</h3>
  <p class="cr-meta text-body-secondary mb-0 px-1 pt-1">30 s · f/2.8 · ISO 3200 · stacked ×8</p>
</article></div>
CSS
/* Dark theme via data-bs-theme="dark"; .card, .btn, .btn-group and .form-range restyled through Bootstrap variables. */
[data-bs-theme="dark"]{--bs-body-bg:#14161c;--bs-border-color:#262a33;--bs-body-color:#e8e9ee;--bs-secondary-color:#9aa0ad}
.ic{--v:50;--thumb:44px;--x:calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--v) / 100);--bs-aspect-ratio:66.6667%;
  overflow:hidden;background:#07080b;isolation:isolate;user-select:none;-webkit-user-select:none}
.ic img{object-fit:cover;pointer-events:none}
.ic-before{clip-path:inset(0 calc(100% - var(--x)) 0 0)}
.ic-before img{filter:var(--ic-before,brightness(1.08) contrast(.68) saturate(.4))}
.ic-tag{top:12px;padding:4px 9px;border-radius:6px;background:rgba(7,8,11,.82);color:#e8e9ee;font:600 .7rem/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;transition:opacity .2s}
.ic .ic-tag-b{left:12px;opacity:clamp(0,calc((var(--v) - 14) / 8),1)}
.ic .ic-tag-a{right:12px;opacity:clamp(0,calc((86 - var(--v)) / 8),1)}
.ic .form-range{z-index:2;opacity:0;cursor:ew-resize}
.ic .form-range::-webkit-slider-runnable-track{height:100%}
.ic .form-range::-webkit-slider-thumb{width:var(--thumb);height:100%;margin-top:0}
.ic .form-range::-moz-range-thumb{width:var(--thumb);height:100%}
.ic-handle{left:var(--x);width:0;pointer-events:none;z-index:1}
.ic-handle::before{content:"";position:absolute;top:0;bottom:0;left:-1px;width:2px;background:#8aa8ff;box-shadow:0 0 12px rgba(138,168,255,.6)}
.ic-knob{position:absolute;top:50%;left:0;display:grid;place-items:center;width:30px;height:52px;translate:-50% -50%;border-radius:8px;background:#8aa8ff;color:#0b0c10;box-shadow:0 4px 16px rgba(0,0,0,.5)}
.ic-knob svg{width:16px;height:16px}
.ic .form-range:focus-visible ~ .ic-handle .ic-knob{outline:3px solid #fff;outline-offset:3px}

/* 1. Editor */
.ed{--bs-card-border-radius:16px;--bs-card-cap-bg:transparent;--bs-card-cap-padding-y:12px;--bs-card-cap-padding-x:16px;overflow:hidden;box-shadow:0 30px 60px -30px rgba(0,0,0,.8)}
.ed-title{font-size:.95rem}
.ed-meta{font:500 .78rem/1 ui-monospace,SFMono-Regular,Menlo,monospace}
.ed .btn-primary{--bs-btn-bg:#8aa8ff;--bs-btn-border-color:#8aa8ff;--bs-btn-color:#0b0c10;--bs-btn-hover-bg:#a6bdff;--bs-btn-hover-border-color:#a6bdff;--bs-btn-hover-color:#0b0c10;
  --bs-btn-padding-x:14px;--bs-btn-padding-y:8px;--bs-btn-font-size:.84rem;--bs-btn-font-weight:700;--bs-btn-border-radius:8px;line-height:normal}
@media (min-width:820px){.ed-main{grid-template-columns:1fr 250px}.ed-side{border-top:0!important;border-left:1px solid var(--bs-border-color)}}
.ed-side{padding:16px 18px}
.ed-side h4{margin-bottom:12px;font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#9aa0ad}
.ed-adj{display:grid;gap:12px;margin:0;font-size:.84rem}
.ed-adj-row{display:grid;grid-template-columns:1fr auto;gap:6px}
.ed-adj dt{font-weight:400;color:#c3c7d1}
.ed-adj dd{margin:0;font:600 .8rem/1.4 ui-monospace,SFMono-Regular,Menlo,monospace}
.ed-adj i{grid-column:1/-1;position:relative;height:4px;border-radius:2px;background:#262a33}
.ed-adj i::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:calc(var(--a) * .5%);border-radius:2px;background:#8aa8ff}
.ed-adj i.neg::after{left:auto;right:50%}
.ed .card-footer{padding:10px 16px;font-size:.8rem}
.ed-views{padding:3px;gap:2px;border-radius:9px;background:#0b0c10}
.ed-views .btn{--bs-btn-padding-x:12px;--bs-btn-padding-y:6px;--bs-btn-font-size:.8rem;--bs-btn-font-weight:600;--bs-btn-color:#c3c7d1;--bs-btn-border-width:0;--bs-btn-hover-color:#fff;
  --bs-btn-focus-box-shadow:none;border-radius:7px!important;line-height:normal}
.ed-views .btn[aria-pressed="true"]{background:#262a33;color:#fff}
.ed-views .btn:focus-visible,.ed .btn-primary:focus-visible{outline:2px solid #8aa8ff;outline-offset:2px;box-shadow:none}
.ed[data-view="side"] .ic-before{clip-path:inset(0 50% 0 0)}
.ed[data-view="side"] .ic-before img{width:50%!important}
.ed[data-view="side"] .ic > img{left:50%;width:50%}
.ed[data-view="side"] .ic-handle{left:50%}
.ed[data-view="side"] .ic-knob,.ed[data-view="edit"] .ic-handle,.ed[data-view="edit"] .ic-before,.ed[data-view="edit"] .ic-tag-b{display:none}
.ed[data-view="side"] .ic .ic-tag{opacity:1}
.ed:not([data-view="split"]) .ic .form-range{display:none}

/* 2. Compact card with a visible .form-range */
.cr{--bs-card-border-radius:18px;max-width:520px;padding:14px}
.cr .ic{--bs-aspect-ratio:62.5%;border-radius:10px}
.cr .ic > img{filter:brightness(.6) contrast(.85) saturate(.5)}
.cr .ic-before img{filter:none}
.cr-row label{font-size:.84rem;font-weight:600}
.cr .form-range{height:24px}
.cr .form-range::-webkit-slider-runnable-track{height:6px;border-radius:3px;background:linear-gradient(90deg,#8aa8ff var(--fill,50%),#3a3f4b 0)}
.cr .form-range::-moz-range-track{height:6px;border-radius:3px;background:#3a3f4b}
.cr .form-range::-moz-range-progress{height:6px;border-radius:3px;background:#8aa8ff}
.cr .form-range::-webkit-slider-thumb{width:20px;height:20px;margin-top:-7px;background:#fff;border:3px solid #8aa8ff}
.cr .form-range::-moz-range-thumb{width:14px;height:14px;background:#fff;border:3px solid #8aa8ff}
.cr .form-range:focus::-webkit-slider-thumb,.cr .form-range:focus::-moz-range-thumb{box-shadow:none}
.cr .form-range:focus-visible{outline:2px solid #8aa8ff;outline-offset:4px;border-radius:4px}
.cr output{min-width:4.5ch;font:600 .82rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#c3c7d1;text-align:right}
.cr-title{font-size:.95rem}
.cr-meta{font-size:.82rem}
@media (prefers-reduced-motion:reduce){.ic-tag{transition:none}}
JavaScript
// Compare: the range input is the control. Its value goes to --v on the stage; a range
// outside the stage (the compact card) finds its stage in the same card.
document.querySelectorAll('[data-compare-range]').forEach((range) => {
  const card = range.closest('[data-compare]') || range.closest('article, section');
  const stage = card.matches('[data-compare]') ? card : card.querySelector('[data-compare]');
  const out = card.querySelector('output');
  const sync = () => {
    stage.style.setProperty('--v', range.value);
    range.style.setProperty('--fill', `${range.value}%`);
    const after = stage.hasAttribute('data-after-left') ? range.value : 100 - range.value;
    range.setAttribute('aria-valuetext', `${after}% after`);
    if (out) out.textContent = `${range.value}%`;
  };
  range.addEventListener('input', sync);
  sync();
});
// Editor view modes: one pressed button at a time; data-view on the editor switches the layout.
document.querySelectorAll('[data-view]').forEach((ed) => {
  const btns = [...ed.querySelectorAll('[data-view-btn]')];
  btns.forEach((btn) => btn.addEventListener('click', () => {
    ed.dataset.view = btn.dataset.viewBtn;
    btns.forEach((b) => b.setAttribute('aria-pressed', b === btn));
  }));
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa0ad] uppercase">Photo editor with view modes</span>
<section class="group/ed overflow-hidden rounded-2xl border border-[#262a33] bg-[#14161c] shadow-[0_30px_60px_-30px_rgba(0,0,0,.8)]" data-view="split" aria-labelledby="edt-title">
  <div class="flex flex-wrap items-center justify-between gap-x-[18px] gap-y-2.5 border-b border-[#262a33] px-4 py-3">
    <div class="flex min-w-0 items-center gap-2.5"><h3 id="edt-title" class="text-[.95rem] font-semibold">Neon Alley</h3><span class="font-mono text-[.78rem] leading-none font-medium text-[#9aa0ad]">DSC_0418.ARW · 6000 × 4000</span></div>
    <button class="cursor-pointer rounded-lg bg-[#8aa8ff] px-3.5 py-2 text-[.84rem] font-bold text-[#0b0c10] hover:bg-[#a6bdff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8aa8ff]" type="button">Export</button>
  </div>
  <div class="grid min-[820px]:grid-cols-[1fr_250px]">
    <div class="compare group relative isolate block aspect-[3/2] overflow-hidden bg-[#07080b] select-none [--thumb:44px] [--v:50]" data-compare>
      <img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" class="pointer-events-none absolute inset-0 block size-full object-cover group-data-[view=side]/ed:left-1/2 group-data-[view=side]/ed:w-1/2" loading="lazy" alt="Edit: a Tokyo shopping street lit by bright pink, blue and teal neon signs at dusk, with people crossing">
      <div class="clip-before absolute inset-0 group-data-[view=edit]/ed:hidden group-data-[view=side]/ed:[clip-path:inset(0_50%_0_0)]"><img src="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1090px) 790px, (min-width: 820px) calc(100vw - 302px), calc(100vw - 34px)" width="1200" height="800" class="pointer-events-none absolute inset-0 block size-full object-cover brightness-108 contrast-68 saturate-40 group-data-[view=side]/ed:w-1/2" loading="lazy" alt="Original: the same street as a flat, washed-out camera file"></div>
      <span class="pointer-events-none absolute top-3 rounded-md bg-[rgba(7,8,11,.82)] px-[9px] py-1 font-mono text-[.7rem] leading-[1.2] font-semibold tracking-[.04em] text-[#e8e9ee] transition-opacity duration-200 motion-reduce:transition-none left-3 opacity-[clamp(0,calc((var(--v)_-_14)/8),1)] group-data-[view=edit]/ed:hidden group-data-[view=side]/ed:opacity-100" aria-hidden="true">ORIGINAL</span><span class="pointer-events-none absolute top-3 rounded-md bg-[rgba(7,8,11,.82)] px-[9px] py-1 font-mono text-[.7rem] leading-[1.2] font-semibold tracking-[.04em] text-[#e8e9ee] transition-opacity duration-200 motion-reduce:transition-none right-3 opacity-[clamp(0,calc((86_-_var(--v))/8),1)] group-data-[view=side]/ed:opacity-100" aria-hidden="true">EDIT</span>
      <input class="compare-range peer absolute inset-0 z-2 m-0 size-full cursor-ew-resize opacity-0 group-data-[view=edit]/ed:hidden group-data-[view=side]/ed:hidden" type="range" min="0" max="100" value="50" aria-label="Compare the original file and the edit" aria-valuetext="50% after" data-compare-range>
      <span class="pointer-events-none absolute inset-y-0 left-(--x) z-1 w-0.5 -translate-x-1/2 bg-[#8aa8ff] shadow-[0_0_12px_rgba(138,168,255,.6)] group-data-[view=edit]/ed:hidden group-data-[view=side]/ed:left-1/2" data-handle aria-hidden="true"></span>
      <span class="pointer-events-none absolute top-1/2 left-(--x) z-1 grid h-[52px] w-[30px] -translate-1/2 place-items-center rounded-lg bg-[#8aa8ff] text-[#0b0c10] shadow-[0_4px_16px_rgba(0,0,0,.5)] group-data-[view=edit]/ed:hidden group-data-[view=side]/ed:hidden peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-white" aria-hidden="true"><svg class="size-4" viewBox="0 0 24 24"><path d="M9 6l-6 6 6 6M15 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
    </div>
    <aside class="border-t border-[#262a33] px-[18px] py-4 min-[820px]:border-t-0 min-[820px]:border-l" aria-label="Adjustments">
      <h4 class="mb-3 text-[.72rem] font-bold tracking-[.12em] text-[#9aa0ad] uppercase">Adjustments</h4>
      <dl class="grid gap-3 text-[.84rem]">
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Exposure</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">+0.35</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:left-1/2 [--a:35]" aria-hidden="true"></i></div>
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Contrast</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">+42</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:left-1/2 [--a:42]" aria-hidden="true"></i></div>
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Highlights</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">−38</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:right-1/2 [--a:38]" aria-hidden="true"></i></div>
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Shadows</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">+24</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:left-1/2 [--a:24]" aria-hidden="true"></i></div>
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Vibrance</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">+56</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:left-1/2 [--a:56]" aria-hidden="true"></i></div>
        <div class="grid grid-cols-[1fr_auto] gap-1.5"><dt class="text-[#c3c7d1]">Clarity</dt><dd class="font-mono text-[.8rem] leading-[1.4] font-semibold">+18</dd><i class="relative col-span-full h-1 rounded-sm bg-[#262a33] after:absolute after:inset-y-0 after:w-[calc(var(--a)*.5%)] after:rounded-sm after:bg-[#8aa8ff] after:left-1/2 [--a:18]" aria-hidden="true"></i></div>
        </dl>
    </aside>
  </div>
  <div class="flex flex-wrap items-center justify-between gap-2.5 border-t border-[#262a33] px-4 py-2.5 text-[.8rem] text-[#9aa0ad]">
    <div class="inline-flex gap-0.5 rounded-[9px] bg-[#0b0c10] p-[3px]" role="group" aria-label="View">
      <button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.8rem] font-semibold text-[#c3c7d1] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8aa8ff] aria-pressed:bg-[#262a33] aria-pressed:text-white" type="button" aria-pressed="true" data-view-btn="split">Split</button>
      <button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.8rem] font-semibold text-[#c3c7d1] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8aa8ff] aria-pressed:bg-[#262a33] aria-pressed:text-white" type="button" aria-pressed="false" data-view-btn="side">Side by side</button>
      <button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.8rem] font-semibold text-[#c3c7d1] hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8aa8ff] aria-pressed:bg-[#262a33] aria-pressed:text-white" type="button" aria-pressed="false" data-view-btn="edit">Edit only</button>
    </div>
    <span>Fit · 18%</span>
  </div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa0ad] uppercase">Compact card with a visible slider</span>
<article class="max-w-[520px] rounded-[18px] border border-[#262a33] bg-[#14161c] p-3.5" aria-labelledby="crt-title">
  <div class="compare relative isolate block aspect-[16/10] overflow-hidden rounded-[10px] bg-[#07080b] select-none [--thumb:44px] [--v:50]" data-compare data-after-left>
    <img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" class="pointer-events-none absolute inset-0 block size-full object-cover brightness-60 contrast-85 saturate-50" loading="lazy" alt="Before: a starry sky over a cone-shaped volcano and a lake, underexposed and murky">
    <div class="clip-before absolute inset-0"><img src="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70" srcset="https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=480&h=300&q=70 480w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=720&h=450&q=70 720w, https://images.unsplash.com/photo-1436891620584-47fd0e565afb?auto=format&fit=crop&w=1040&h=650&q=70 1040w" sizes="(min-width: 560px) 490px, calc(100vw - 62px)" width="1040" height="650" class="pointer-events-none absolute inset-0 block size-full object-cover" loading="lazy" alt="After: the same night sky full of stars over the volcano, reflected in a calm blue lake"></div>
    <span class="pointer-events-none absolute top-3 rounded-md bg-[rgba(7,8,11,.82)] px-[9px] py-1 font-mono text-[.7rem] leading-[1.2] font-semibold tracking-[.04em] text-[#e8e9ee] transition-opacity duration-200 motion-reduce:transition-none left-3 opacity-[clamp(0,calc((var(--v)_-_14)/8),1)]" aria-hidden="true">FINAL</span><span class="pointer-events-none absolute top-3 rounded-md bg-[rgba(7,8,11,.82)] px-[9px] py-1 font-mono text-[.7rem] leading-[1.2] font-semibold tracking-[.04em] text-[#e8e9ee] transition-opacity duration-200 motion-reduce:transition-none right-3 opacity-[clamp(0,calc((86_-_var(--v))/8),1)]" aria-hidden="true">RAW</span>
    <span class="pointer-events-none absolute inset-y-0 left-(--x) z-1 w-0.5 -translate-x-1/2 bg-[#8aa8ff] shadow-[0_0_12px_rgba(138,168,255,.6)]" data-handle aria-hidden="true"></span>
  </div>
  <div class="flex items-center gap-3.5 px-1 pt-4 pb-1">
    <label for="crt-range" class="text-[.84rem] font-semibold whitespace-nowrap">Final</label>
    <input class="range-dark min-w-0 flex-1 focus-visible:rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#8aa8ff]" id="crt-range" type="range" min="0" max="100" value="50" aria-valuetext="50% after" data-compare-range>
    <output for="crt-range" class="min-w-[4.5ch] text-right font-mono text-[.82rem] leading-none font-semibold text-[#c3c7d1]" aria-hidden="true">50%</output>
  </div>
  <h3 id="crt-title" class="px-1 pt-0.5 text-[.95rem] font-semibold">Fuji from Lake Motosu</h3>
  <p class="px-1 pt-0.5 text-[.82rem] text-[#9aa0ad]">30 s · f/2.8 · ISO 3200 · stacked ×8</p>
</article></div>
CSS
/* 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; }
}
/* Visible slider for the compact card: a filled track and a ringed thumb */
@utility range-dark {
  -webkit-appearance: none; appearance: none; background: none; height: 24px; cursor: pointer;
  &::-webkit-slider-runnable-track { height: 6px; border-radius: 3px; background: linear-gradient(90deg, #8aa8ff var(--fill, 50%), #3a3f4b 0); }
  &::-moz-range-track { height: 6px; border-radius: 3px; background: #3a3f4b; }
  &::-moz-range-progress { height: 6px; border-radius: 3px; background: #8aa8ff; }
  &::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; margin-top: -7px; border-radius: 50%; background: #fff; border: 3px solid #8aa8ff; }
  &::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: #fff; border: 3px solid #8aa8ff; }
}
JavaScript
// Compare: the range input is the control. Its value goes to --v on the stage; a range
// outside the stage (the compact card) finds its stage in the same card.
document.querySelectorAll('[data-compare-range]').forEach((range) => {
  const card = range.closest('[data-compare]') || range.closest('article, section');
  const stage = card.matches('[data-compare]') ? card : card.querySelector('[data-compare]');
  const out = card.querySelector('output');
  const sync = () => {
    stage.style.setProperty('--v', range.value);
    range.style.setProperty('--fill', `${range.value}%`);
    const after = stage.hasAttribute('data-after-left') ? range.value : 100 - range.value;
    range.setAttribute('aria-valuetext', `${after}% after`);
    if (out) out.textContent = `${range.value}%`;
  };
  range.addEventListener('input', sync);
  sync();
});
// Editor view modes: one pressed button at a time; data-view on the editor switches the layout.
document.querySelectorAll('[data-view]').forEach((ed) => {
  const btns = [...ed.querySelectorAll('[data-view-btn]')];
  btns.forEach((btn) => btn.addEventListener('click', () => {
    ed.dataset.view = btn.dataset.viewBtn;
    btns.forEach((b) => b.setAttribute('aria-pressed', b === btn));
  }));
});

About this set

A comparison slider dressed as a photo editor, for photo apps, preset shops and retouching portfolios on a dark interface. Photo editor with view modes has a title bar with the file name and an Export button, a neon street photo with a blue divider and a tall handle, an adjustments panel listing exposure, contrast, highlights, shadows, vibrance and clarity with small bipolar bars, and a footer with three view buttons: Split, Side by side and Edit only. The buttons are a group with aria-pressed, and a data-view attribute on the editor switches the layout with CSS: side by side shows both versions whole, and edit only hides the original and the slider. Compact card with a visible slider is a smaller card for a night sky photo, with the range drawn as a filled track under the photo rather than over it, a live percentage readout and the exposure details. That pattern suits pages where an invisible control over the image might not be discovered. Colours are checked for contrast on the dark ground, and labels use a monospaced face to match editing software.

What’s included

  • Editor frame with file bar, adjustments panel and view buttons
  • Split, side by side and edit only view modes switched with one attribute
  • Visible range with a filled track and a live readout in the compact card
  • Contrast-checked colours on a near-black ground
  • Bootstrap version runs in data-bs-theme dark with .card, .btn-group and .form-range

Accessibility

The overlay slider and the visible slider are native range inputs with a label and an aria-valuetext such as 62% after; the visible one has a real label element. The view buttons are a labelled group of toggle buttons using aria-pressed, and every control has a visible focus outline in the accent blue or white. The adjustment bars are hidden from screen readers because the values are written out beside them, and the label fade is switched off under prefers-reduced-motion.

Customise it

Change the accent #8aa8ff, the panel colours in .ed and .cr, and the before filter in --ic-before. In Bootstrap override --bs-body-bg and --bs-border-color under data-bs-theme dark and the --bs-btn-* variables; in Tailwind edit the colour utilities and the range-dark utility.