Open demo

CSS code

HTML
<div><span class="lbl">Tabs: original, edited, retouched</span>
<section class="tc" data-tabs aria-labelledby="tc-title">
  <div class="tc-panels">
    <figure class="tc-panel tc-photo tc-original" id="tc-p1" role="tabpanel" aria-labelledby="tc-t1"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Original: portrait of a young woman with a copper bob in front of a blurred lake, flat and slightly dull"><figcaption>Original</figcaption></figure>
    <figure class="tc-panel tc-photo is-active" id="tc-p2" role="tabpanel" aria-labelledby="tc-t2"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Edited: the same portrait with balanced colour and contrast"><figcaption>Edited</figcaption></figure>
    <figure class="tc-panel tc-photo tc-retouched" id="tc-p3" role="tabpanel" aria-labelledby="tc-t3"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Retouched: the same portrait, warmer and brighter, with richer skin tones"><figcaption>Retouched</figcaption></figure>
  </div>
  <div class="tc-side">
    <p class="tc-kicker">Portrait session</p>
    <h3 id="tc-title">Ellie at Lake Bled</h3>
    <p class="tc-lead">Three stages of the same frame. Switch between them to see what each pass of the edit changed.</p>
    <div class="tc-list" role="tablist" aria-label="Edit stage">
      <button class="tc-tab" id="tc-t1" role="tab" aria-selected="false" aria-controls="tc-p1" tabindex="-1">Original</button>
      <button class="tc-tab" id="tc-t2" role="tab" aria-selected="true" aria-controls="tc-p2">Edited</button>
      <button class="tc-tab" id="tc-t3" role="tab" aria-selected="false" aria-controls="tc-p3" tabindex="-1">Retouched</button>
    </div>
    <dl class="tc-notes"><dt>Camera</dt><dd>85 mm · f/1.8 · ISO 200</dd><dt>Edit</dt><dd>White balance, contrast curve</dd><dt>Retouch</dt><dd>Warm tone, skin and eye light</dd></dl>
  </div>
</section></div>

<div><span class="lbl">Fade toggle with aria-pressed</span>
<figure class="ft">
  <div class="ft-stage">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="Styled: a bowl of avocado, chickpeas, tomatoes, roast sweet potato and greens on a wooden table, in fresh bright colour">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" class="ft-before" loading="lazy" alt="Unedited: the same bowl, dim and yellowish straight from the camera">
  </div>
  <figcaption class="ft-bar">
    <span><strong>Harvest bowl, Verde Kitchen menu</strong><span>Food styling and grade by Jonas Weber</span></span>
    <button class="ft-btn" type="button" aria-pressed="false" data-toggle-original><i aria-hidden="true"></i>Show original</button>
  </figcaption>
</figure></div>
CSS
/* 1. Tabs: Original / Edited / Retouched. Three panels stack in one grid cell and cross-fade;
      the tabs follow the ARIA tabs pattern (arrow keys, Home, End). */
.tc{--tc-accent:#16181d;--tc-focus:#1d4ed8;display:grid;gap:28px;align-items:start}
@media (min-width:760px){.tc{grid-template-columns:minmax(0,420px) 1fr}}
.tc-list{display:flex;gap:4px;padding:4px;border-radius:999px;background:#e4e0d8;width:max-content;max-width:100%}
.tc-tab{padding:9px 16px;border:0;border-radius:999px;background:none;font:inherit;font-size:.86rem;font-weight:600;color:#3d3a35;cursor:pointer;transition:background .2s,color .2s}
.tc-tab[aria-selected="true"]{background:var(--tc-accent);color:#fff}
.tc-tab:focus-visible{outline:3px solid var(--tc-focus);outline-offset:2px}
.tc-panels{display:grid}
.tc-panel{grid-area:1/1;transition:opacity .35s,visibility .35s}
.tc-panel:not(.is-active){opacity:0;visibility:hidden}
.tc-photo{position:relative;aspect-ratio:4/5;overflow:hidden;border-radius:18px;background:#d8d3ca}
.tc-photo img{width:100%;height:100%;object-fit:cover;display:block}
.tc-photo figcaption{position:absolute;left:14px;bottom:14px;padding:5px 11px;border-radius:999px;background:rgba(15,17,21,.78);color:#fff;font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.tc-original img{filter:brightness(.93) contrast(.86) saturate(.7)}
.tc-retouched img{filter:brightness(1.06) contrast(1.06) saturate(1.15) sepia(.12)}
.tc-side h3{margin-top:4px;font-size:1.6rem;line-height:1.15;letter-spacing:-.02em}
.tc-kicker{font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#8a3a14}
.tc-lead{margin:10px 0 22px;color:#4d4943;line-height:1.55;max-width:44ch}
.tc-notes{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;margin-top:22px;font-size:.9rem}
.tc-notes dt{color:#5f5a52}.tc-notes dd{font-weight:600}

/* 2. Fade toggle: one button with aria-pressed fades the original in over the edit */
.ft{max-width:760px}
.ft-stage{position:relative;aspect-ratio:3/2;overflow:hidden;border-radius:18px;background:#d8d3ca}
.ft-stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.ft-before{opacity:0;transition:opacity .45s ease;filter:brightness(.86) contrast(.84) saturate(.5) sepia(.18)}
.ft:has([aria-pressed="true"]) .ft-before{opacity:1}
.ft-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin-top:14px}
.ft-bar strong{display:block;font-weight:600;color:#16181d}
.ft-bar span{display:block;margin-top:2px;font-size:.88rem;color:#5f5a52}
.ft-btn{display:inline-flex;align-items:center;gap:10px;padding:10px 18px 10px 12px;border:1.5px solid #16181d;border-radius:999px;background:#fff;color:#16181d;font:inherit;font-size:.88rem;font-weight:600;cursor:pointer;transition:background .2s,color .2s}
.ft-btn i{position:relative;width:34px;height:20px;border-radius:999px;background:#8f887c;transition:background .2s}
.ft-btn i::after{content:"";position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:#fff;transition:translate .2s}
.ft-btn[aria-pressed="true"]{background:#16181d;color:#fff}
.ft-btn[aria-pressed="true"] i{background:#e0703a}
.ft-btn[aria-pressed="true"] i::after{translate:14px 0}
.ft-btn:focus-visible{outline:3px solid #1d4ed8;outline-offset:3px}
@media (prefers-reduced-motion:reduce){.tc-tab,.tc-panel,.ft-before,.ft-btn,.ft-btn i,.ft-btn i::after{transition:none}}
JavaScript
// Tabs: click or arrow keys move the selection; the matching panel fades in.
document.querySelectorAll('[data-tabs]').forEach((root) => {
  const tabs = [...root.querySelectorAll('[role="tab"]')];
  const select = (tab) => {
    tabs.forEach((t) => {
      const on = t === tab;
      t.setAttribute('aria-selected', on);
      t.tabIndex = on ? 0 : -1;
      document.getElementById(t.getAttribute('aria-controls')).classList.toggle('is-active', on);
    });
  };
  tabs.forEach((tab, i) => {
    tab.addEventListener('click', () => select(tab));
    tab.addEventListener('keydown', (e) => {
      const next = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key];
      if (next === undefined) return;
      e.preventDefault();
      const t = tabs[(next + tabs.length) % tabs.length];
      select(t);
      t.focus();
    });
  });
});
// Toggle: aria-pressed is the state; CSS fades the original in when it is true.
document.querySelectorAll('[data-toggle-original]').forEach((btn) => {
  btn.addEventListener('click', () => btn.setAttribute('aria-pressed', btn.getAttribute('aria-pressed') !== 'true'));
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<div><span class="lbl">Tabs: original, edited, retouched</span>
<section class="tc row g-4 align-items-start" aria-labelledby="tcb-title">
  <div class="col-12 tc-col-photo">
    <div class="tab-content">
      <figure class="tab-pane fade tc-photo tc-original ratio m-0" id="tcb-p1" role="tabpanel" aria-labelledby="tcb-t1"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 768px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Original: portrait of a young woman with a copper bob in front of a blurred lake, flat and slightly dull"><figcaption class="badge rounded-pill position-absolute">Original</figcaption></figure>
      <figure class="tab-pane fade show active tc-photo ratio m-0" id="tcb-p2" role="tabpanel" aria-labelledby="tcb-t2"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 768px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Edited: the same portrait with balanced colour and contrast"><figcaption class="badge rounded-pill position-absolute">Edited</figcaption></figure>
      <figure class="tab-pane fade tc-photo tc-retouched ratio m-0" id="tcb-p3" role="tabpanel" aria-labelledby="tcb-t3"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 768px) 420px, calc(100vw - 32px)" width="840" height="1050" loading="lazy" alt="Retouched: the same portrait, warmer and brighter, with richer skin tones"><figcaption class="badge rounded-pill position-absolute">Retouched</figcaption></figure>
    </div>
  </div>
  <div class="col">
    <p class="tc-kicker mb-0">Portrait session</p>
    <h3 id="tcb-title" class="tc-title fw-bold mt-1 mb-0">Ellie at Lake Bled</h3>
    <p class="tc-lead mt-2 mb-4">Three stages of the same frame. Switch between them to see what each pass of the edit changed.</p>
    <div class="nav nav-pills" role="tablist" aria-label="Edit stage">
      <button class="nav-link" id="tcb-t1" data-bs-toggle="tab" data-bs-target="#tcb-p1" type="button" role="tab" aria-controls="tcb-p1" aria-selected="false" tabindex="-1">Original</button>
      <button class="nav-link active" id="tcb-t2" data-bs-toggle="tab" data-bs-target="#tcb-p2" type="button" role="tab" aria-controls="tcb-p2" aria-selected="true">Edited</button>
      <button class="nav-link" id="tcb-t3" data-bs-toggle="tab" data-bs-target="#tcb-p3" type="button" role="tab" aria-controls="tcb-p3" aria-selected="false" tabindex="-1">Retouched</button>
    </div>
    <dl class="tc-notes mt-4 mb-0"><dt>Camera</dt><dd>85 mm · f/1.8 · ISO 200</dd><dt>Edit</dt><dd>White balance, contrast curve</dd><dt>Retouch</dt><dd>Warm tone, skin and eye light</dd></dl>
  </div>
</section></div>

<div><span class="lbl">Fade toggle with aria-pressed</span>
<figure class="ft m-0">
  <div class="ft-stage ratio">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="Styled: a bowl of avocado, chickpeas, tomatoes, roast sweet potato and greens on a wooden table, in fresh bright colour">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" class="ft-before" loading="lazy" alt="Unedited: the same bowl, dim and yellowish straight from the camera">
  </div>
  <figcaption class="d-flex flex-wrap align-items-center justify-content-between gap-3 mt-3">
    <span><strong class="d-block fw-semibold">Harvest bowl, Verde Kitchen menu</strong><span class="ft-meta d-block mt-1">Food styling and grade by Jonas Weber</span></span>
    <button class="btn d-inline-flex align-items-center gap-2" type="button" data-bs-toggle="button" aria-pressed="false"><i class="ft-track" aria-hidden="true"></i>Show original</button>
  </figcaption>
</figure></div>
CSS
/* 1. Tabs on Bootstrap's .nav-pills and .tab-pane.fade (the bundle's tab plugin handles roles and arrow keys) */
.tc{--tc-focus:#1d4ed8}
@media (min-width:768px){.tc-col-photo{flex:0 0 auto;width:min(444px,48%)}}
.tc .nav-pills{--bs-nav-pills-border-radius:999px;--bs-nav-pills-link-active-bg:#16181d;--bs-nav-link-padding-x:16px;--bs-nav-link-padding-y:9px;
  --bs-nav-link-font-size:.86rem;--bs-nav-link-font-weight:600;--bs-nav-link-color:#3d3a35;--bs-nav-link-hover-color:#16181d;
  gap:4px;padding:4px;border-radius:999px;background:#e4e0d8;width:max-content;max-width:100%}
.tc .nav-link{line-height:normal}
.tc .nav-link:focus-visible{outline:3px solid var(--tc-focus);outline-offset:2px;box-shadow:none}
.tc-photo{overflow:hidden;border-radius:18px;background:#d8d3ca;--bs-aspect-ratio:125%}
.tc-photo img{object-fit:cover}
.tc-photo .badge{left:14px;bottom:14px;top:auto;width:auto;height:auto;padding:5px 11px;background:rgba(15,17,21,.78);font-size:.72rem;letter-spacing:.08em;text-transform:uppercase}
.tc-original img{filter:brightness(.93) contrast(.86) saturate(.7)}
.tc-retouched img{filter:brightness(1.06) contrast(1.06) saturate(1.15) sepia(.12)}
.tc-kicker{font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#8a3a14}
.tc-title{font-size:1.6rem;line-height:1.15;letter-spacing:-.02em}
.tc-lead{color:#4d4943;line-height:1.55;max-width:44ch}
.tc-notes{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;font-size:.9rem}
.tc-notes dt{font-weight:400;color:#5f5a52}.tc-notes dd{margin:0;font-weight:600}

/* 2. Fade toggle: a .btn with data-bs-toggle="button" keeps aria-pressed in sync; CSS fades the original in */
.ft{max-width:760px}
.ft-stage{overflow:hidden;border-radius:18px;background:#d8d3ca;--bs-aspect-ratio:66.6667%}
.ft-stage img{object-fit:cover}
.ft-before{opacity:0;transition:opacity .45s ease;filter:brightness(.86) contrast(.84) saturate(.5) sepia(.18)}
.ft:has([aria-pressed="true"]) .ft-before{opacity:1}
.ft-meta{font-size:.88rem;color:#5f5a52}
.ft .btn{--bs-btn-padding-x:18px;--bs-btn-padding-y:10px;--bs-btn-font-size:.88rem;--bs-btn-font-weight:600;--bs-btn-border-width:1.5px;
  --bs-btn-color:#16181d;--bs-btn-bg:#fff;--bs-btn-border-color:#16181d;--bs-btn-hover-color:#16181d;--bs-btn-hover-bg:#f3f1ec;--bs-btn-hover-border-color:#16181d;
  --bs-btn-active-color:#fff;--bs-btn-active-bg:#16181d;--bs-btn-active-border-color:#16181d;
  padding-left:12px;border-radius:999px;line-height:normal;transition:background .2s,color .2s}
.ft .btn:focus-visible{outline:3px solid #1d4ed8;outline-offset:3px;box-shadow:none}
.ft-track{position:relative;width:34px;height:20px;border-radius:999px;background:#8f887c;transition:background .2s}
.ft-track::after{content:"";position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:#fff;transition:translate .2s}
.ft .btn.active .ft-track{background:#e0703a}
.ft .btn.active .ft-track::after{translate:14px 0}
@media (prefers-reduced-motion:reduce){.tc .nav-link,.tab-pane.fade,.ft-before,.ft .btn,.ft-track,.ft-track::after{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5f5a52] uppercase">Tabs: original, edited, retouched</span>
<section class="grid items-start gap-7 min-[760px]:grid-cols-[minmax(0,420px)_1fr]" data-tabs aria-labelledby="tct-title">
  <div class="grid">
    <figure class="relative col-start-1 row-start-1 aspect-[4/5] overflow-hidden rounded-[18px] bg-[#d8d3ca] transition-[opacity,visibility] duration-[350ms] motion-reduce:transition-none [&:not(.is-active)]:invisible [&:not(.is-active)]:opacity-0" id="tct-p1" role="tabpanel" aria-labelledby="tct-t1"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" class="block size-full object-cover brightness-93 contrast-86 saturate-70" loading="lazy" alt="Original: portrait of a young woman with a copper bob in front of a blurred lake, flat and slightly dull"><figcaption class="absolute bottom-3.5 left-3.5 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase">Original</figcaption></figure>
    <figure class="is-active relative col-start-1 row-start-1 aspect-[4/5] overflow-hidden rounded-[18px] bg-[#d8d3ca] transition-[opacity,visibility] duration-[350ms] motion-reduce:transition-none [&:not(.is-active)]:invisible [&:not(.is-active)]:opacity-0" id="tct-p2" role="tabpanel" aria-labelledby="tct-t2"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" class="block size-full object-cover" loading="lazy" alt="Edited: the same portrait with balanced colour and contrast"><figcaption class="absolute bottom-3.5 left-3.5 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase">Edited</figcaption></figure>
    <figure class="relative col-start-1 row-start-1 aspect-[4/5] overflow-hidden rounded-[18px] bg-[#d8d3ca] transition-[opacity,visibility] duration-[350ms] motion-reduce:transition-none [&:not(.is-active)]:invisible [&:not(.is-active)]:opacity-0" id="tct-p3" role="tabpanel" aria-labelledby="tct-t3"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70" srcset="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=420&h=525&crop=faces&q=70 420w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=640&h=800&crop=faces&q=70 640w, https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=840&h=1050&crop=faces&q=70 840w" sizes="(min-width: 760px) 420px, calc(100vw - 32px)" width="840" height="1050" class="block size-full object-cover brightness-106 contrast-106 saturate-115 sepia-12" loading="lazy" alt="Retouched: the same portrait, warmer and brighter, with richer skin tones"><figcaption class="absolute bottom-3.5 left-3.5 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase">Retouched</figcaption></figure>
  </div>
  <div>
    <p class="text-xs font-bold tracking-[.14em] text-[#8a3a14] uppercase">Portrait session</p>
    <h3 id="tct-title" class="mt-1 text-[1.6rem] leading-[1.15] font-bold tracking-[-.02em]">Ellie at Lake Bled</h3>
    <p class="mt-2.5 mb-[22px] max-w-[44ch] leading-[1.55] text-[#4d4943]">Three stages of the same frame. Switch between them to see what each pass of the edit changed.</p>
    <div class="flex w-max max-w-full gap-1 rounded-full bg-[#e4e0d8] p-1" role="tablist" aria-label="Edit stage">
      <button class="cursor-pointer rounded-full px-4 py-[9px] text-[.86rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1d4ed8] aria-selected:bg-[#16181d] aria-selected:text-white motion-reduce:transition-none" id="tct-t1" role="tab" aria-selected="false" aria-controls="tct-p1" tabindex="-1">Original</button>
      <button class="cursor-pointer rounded-full px-4 py-[9px] text-[.86rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1d4ed8] aria-selected:bg-[#16181d] aria-selected:text-white motion-reduce:transition-none" id="tct-t2" role="tab" aria-selected="true" aria-controls="tct-p2">Edited</button>
      <button class="cursor-pointer rounded-full px-4 py-[9px] text-[.86rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1d4ed8] aria-selected:bg-[#16181d] aria-selected:text-white motion-reduce:transition-none" id="tct-t3" role="tab" aria-selected="false" aria-controls="tct-p3" tabindex="-1">Retouched</button>
    </div>
    <dl class="mt-[22px] grid grid-cols-[auto_1fr] gap-x-[18px] gap-y-2 text-[.9rem]"><dt class="text-[#5f5a52]">Camera</dt><dd class="font-semibold">85 mm · f/1.8 · ISO 200</dd><dt class="text-[#5f5a52]">Edit</dt><dd class="font-semibold">White balance, contrast curve</dd><dt class="text-[#5f5a52]">Retouch</dt><dd class="font-semibold">Warm tone, skin and eye light</dd></dl>
  </div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5f5a52] uppercase">Fade toggle with aria-pressed</span>
<figure class="group/ft max-w-[760px]">
  <div class="relative aspect-[3/2] overflow-hidden rounded-[18px] bg-[#d8d3ca]">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" class="absolute inset-0 block size-full object-cover" loading="lazy" alt="Styled: a bowl of avocado, chickpeas, tomatoes, roast sweet potato and greens on a wooden table, in fresh bright colour">
    <img src="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 800px) 760px, calc(100vw - 32px)" width="1200" height="800" class="absolute inset-0 block size-full object-cover opacity-0 brightness-86 contrast-84 saturate-50 sepia-18 transition-opacity duration-[450ms] group-has-[[aria-pressed=true]]/ft:opacity-100 motion-reduce:transition-none" loading="lazy" alt="Unedited: the same bowl, dim and yellowish straight from the camera">
  </div>
  <figcaption class="mt-3.5 flex flex-wrap items-center justify-between gap-3">
    <span><strong class="block font-semibold">Harvest bowl, Verde Kitchen menu</strong><span class="mt-0.5 block text-[.88rem] text-[#5f5a52]">Food styling and grade by Jonas Weber</span></span>
    <button class="group inline-flex cursor-pointer items-center gap-2.5 rounded-full border-[1.5px] border-[#16181d] bg-white py-2.5 pr-[18px] pl-3 text-[.88rem] font-semibold text-[#16181d] transition-[background-color,color] duration-200 hover:bg-[#f3f1ec] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#1d4ed8] aria-pressed:bg-[#16181d] aria-pressed:text-white motion-reduce:transition-none" type="button" aria-pressed="false" data-toggle-original><i class="relative h-5 w-[34px] rounded-full bg-[#8f887c] transition-colors duration-200 after:absolute after:top-[3px] after:left-[3px] after:size-3.5 after:rounded-full after:bg-white after:transition-transform after:duration-200 group-aria-pressed:bg-[#e0703a] group-aria-pressed:after:translate-x-3.5 motion-reduce:transition-none motion-reduce:after:transition-none" aria-hidden="true"></i>Show original</button>
  </figcaption>
</figure></div>
JavaScript
// Tabs: click or arrow keys move the selection; the matching panel fades in.
document.querySelectorAll('[data-tabs]').forEach((root) => {
  const tabs = [...root.querySelectorAll('[role="tab"]')];
  const select = (tab) => {
    tabs.forEach((t) => {
      const on = t === tab;
      t.setAttribute('aria-selected', on);
      t.tabIndex = on ? 0 : -1;
      document.getElementById(t.getAttribute('aria-controls')).classList.toggle('is-active', on);
    });
  };
  tabs.forEach((tab, i) => {
    tab.addEventListener('click', () => select(tab));
    tab.addEventListener('keydown', (e) => {
      const next = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key];
      if (next === undefined) return;
      e.preventDefault();
      const t = tabs[(next + tabs.length) % tabs.length];
      select(t);
      t.focus();
    });
  });
});
// Toggle: aria-pressed is the state; CSS fades the original in when it is true.
document.querySelectorAll('[data-toggle-original]').forEach((btn) => {
  btn.addEventListener('click', () => btn.setAttribute('aria-pressed', btn.getAttribute('aria-pressed') !== 'true'));
});

About this set

Comparisons that switch between whole images instead of splitting one. Tabs for original, edited and retouched show three stages of a portrait session beside a short description and a camera and edit summary. The three panels sit in one grid cell and cross-fade, so the photo never jumps, and the tabs follow the ARIA tabs pattern: the selected tab is in the tab order, the arrow keys move between tabs and Home and End jump to the first and last. Use it when there are more than two versions, such as a retoucher’s process, a colour grading preset or a photo restoration. Fade toggle with aria-pressed is one button under a food photo: pressing Show original fades the unedited shot in over the styled one, and pressing it again fades it out. The state lives in aria-pressed, the switch drawn inside the button follows it with CSS, and :has() on the figure fades the photo, so the script only flips one attribute. The three portrait stages and the food pair are each one Unsplash image with different CSS filters. The Bootstrap version uses .nav-pills with the bundle’s tab plugin and a .btn with data-bs-toggle set to button.

What’s included

  • Three-way tabs: original, edited and retouched
  • Panels cross-fade in one grid cell without layout shift
  • Full keyboard support with arrow keys, Home and End
  • Toggle button whose state is aria-pressed
  • Bootstrap version uses .nav-pills, .tab-pane.fade and the button plugin

Accessibility

The tabs use role tablist, tab and tabpanel with aria-selected, aria-controls and aria-labelledby, and a roving tabindex so only the selected tab is a tab stop. Hidden panels use visibility: hidden, so screen readers skip them. The toggle is a button with aria-pressed and a visible label, every control has a blue focus outline, and the fades are switched off under prefers-reduced-motion.

Customise it

Edit the filters on .tc-original and .tc-retouched and the --tc-accent colour, and the .ft-before filter for the unedited look. In Bootstrap change the --bs-nav-pills-* and --bs-btn-* variables; in Tailwind edit the aria-selected: and aria-pressed: utilities and the filter classes on each image.