Open demo

CSS code

HTML
<div><span class="lbl">Radio buttons and :has(), no JavaScript</span>
<figure class="rc">
  <div class="cc-stage">
    <img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="After: a wooden jetty stretching into a deep blue lake lined with green forest, in full colour">
    <div class="cc-top"><img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="Before: the same jetty and lake almost colourless and flat"></div>
    <span class="cc-line" aria-hidden="true"></span>
    <span class="cc-tag cc-tag-b" aria-hidden="true">Before</span><span class="cc-tag cc-tag-a" aria-hidden="true">After</span>
  </div>
  <fieldset class="rc-switch">
    <legend>Lake jetty, Ontario</legend>
    <span class="rc-seg">
      <label><input type="radio" name="rc-view" value="before"><span>Before</span></label>
      <label><input type="radio" name="rc-view" value="split" checked><span>Split</span></label>
      <label><input type="radio" name="rc-view" value="after"><span>After</span></label>
    </span>
  </fieldset>
</figure></div>

<div><span class="lbl">resize: horizontal, drag the corner grip</span>
<figure>
  <div class="cc-stage rz">
    <img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="After: a pine forest and a wide glacial valley below snowy mountains under a bright blue sky">
    <div class="rz-before"><img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="Before: the same valley in dull, faded colour"><span class="rz-grip" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M14 6L6 14M14 10l-4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span></div>
    <span class="cc-tag cc-tag-b" aria-hidden="true">Before</span><span class="cc-tag cc-tag-a" aria-hidden="true">After</span>
  </div>
  <figcaption class="cc-note"><strong>Kluane valley.</strong> Mouse and trackpad only: resize has no keyboard or touch support, so use it for internal tools or pair it with the radio version above.</figcaption>
</figure></div>
CSS
/* Shared */
.cc-stage{position:relative;aspect-ratio:3/2;overflow:hidden;border-radius:16px;background:#d8d3ca;isolation:isolate}
.cc-stage > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.cc-tag{position:absolute;top:14px;z-index:2;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;pointer-events:none}
.cc-tag-b{left:14px}.cc-tag-a{right:14px}
.cc-note{margin-top:12px;font-size:.88rem;color:#5f5a52}
.cc-note strong{color:#16181d;font-weight:600}

/* 1. Radio buttons and :has(). Each radio sets --v on the figure; the clip and divider follow with a transition.
      Keyboard: Tab to the group, arrow keys to change. No JavaScript. */
.rc{--v:50;--ic-focus:#1d4ed8}
.rc:has([value="before"]:checked){--v:100}
.rc:has([value="after"]:checked){--v:0}
.rc .cc-top{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--v) * 1%) 0 0);transition:clip-path .55s cubic-bezier(.3,.7,.2,1)}
.rc .cc-top img{width:100%;height:100%;object-fit:cover;display:block;filter:brightness(1.08) contrast(.85) saturate(.2)}
.rc .cc-line{position:absolute;top:0;bottom:0;left:calc(var(--v) * 1%);width:3px;translate:-50% 0;background:#fff;box-shadow:0 0 10px rgba(0,0,0,.35);transition:left .55s cubic-bezier(.3,.7,.2,1)}
.rc-switch{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin-top:14px;border:0}
.rc-switch legend{float:left;font-size:.92rem;font-weight:600}
.rc-seg{display:inline-flex;padding:4px;border-radius:999px;background:#e4e0d8}
.rc-seg label{position:relative;cursor:pointer}
.rc-seg input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
.rc-seg span{display:block;padding:8px 16px;border-radius:999px;font-size:.85rem;font-weight:600;color:#3d3a35;transition:background .2s,color .2s}
.rc-seg input:checked + span{background:#16181d;color:#fff}
.rc-seg input:focus-visible + span{outline:3px solid var(--ic-focus);outline-offset:2px}

/* 2. resize: horizontal. The "before" layer is a resizable box; dragging the browser's corner grip resizes it.
      Mouse and trackpad only: resize is not reachable by keyboard and not supported on most touch screens. */
.rz{container-type:inline-size}
.rz-before{position:absolute;top:0;left:0;bottom:0;z-index:1;width:50%;min-width:40px;max-width:100%;overflow:hidden;resize:horizontal;border-right:3px solid #fff}
.rz-before img{position:absolute;top:0;left:0;width:100cqw;max-width:none;height:100%;object-fit:cover;display:block;filter:brightness(.95) contrast(.85) saturate(.6) sepia(.4)}
.rz-grip{position:absolute;right:0;bottom:0;display:grid;place-items:center;width:30px;height:30px;border-radius:8px 0 0 0;background:#fff;color:#16181d;pointer-events:none}
.rz-grip svg{width:16px;height:16px}
@media (prefers-reduced-motion:reduce){.rc .cc-top,.rc .cc-line,.rc-seg span{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Radio buttons and :has(), no JavaScript</span>
<figure class="rc m-0">
  <div class="cc-stage ratio rounded-4">
    <img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="After: a wooden jetty stretching into a deep blue lake lined with green forest, in full colour">
    <div class="cc-top"><img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" class="w-100 h-100" loading="lazy" alt="Before: the same jetty and lake almost colourless and flat"></div>
    <div class="pe-none"><span class="cc-line position-absolute top-0 bottom-0 h-100" aria-hidden="true"></span><span class="badge rounded-pill position-absolute cc-tag-b" aria-hidden="true">Before</span><span class="badge rounded-pill position-absolute cc-tag-a" aria-hidden="true">After</span></div>
  </div>
  <fieldset class="d-flex flex-wrap align-items-center justify-content-between gap-3 mt-3">
    <legend class="rc-legend float-start w-auto m-0 fw-semibold">Lake jetty, Ontario</legend>
    <span class="rc-seg btn-group">
      <input type="radio" class="btn-check" name="rcb-view" id="rcb-before" value="before"><label class="btn" for="rcb-before">Before</label>
      <input type="radio" class="btn-check" name="rcb-view" id="rcb-split" value="split" checked><label class="btn" for="rcb-split">Split</label>
      <input type="radio" class="btn-check" name="rcb-view" id="rcb-after" value="after"><label class="btn" for="rcb-after">After</label>
    </span>
  </fieldset>
</figure></div>

<div><span class="lbl">resize: horizontal, drag the corner grip</span>
<figure class="m-0">
  <div class="cc-stage rz ratio rounded-4">
    <img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="After: a pine forest and a wide glacial valley below snowy mountains under a bright blue sky">
    <div class="rz-before"><img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" loading="lazy" alt="Before: the same valley in dull, faded colour"><span class="rz-grip position-absolute d-grid bg-white pe-none" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M14 6L6 14M14 10l-4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span></div>
    <div class="pe-none"><span class="badge rounded-pill position-absolute cc-tag-b" aria-hidden="true">Before</span><span class="badge rounded-pill position-absolute cc-tag-a" aria-hidden="true">After</span></div>
  </div>
  <figcaption class="cc-note mt-2"><strong class="fw-semibold text-body">Kluane valley.</strong> Mouse and trackpad only: resize has no keyboard or touch support, so use it for internal tools or pair it with the radio version above.</figcaption>
</figure></div>
CSS
/* Shared: .ratio sizes the stage, .badge the labels */
.cc-stage{--bs-aspect-ratio:66.6667%;overflow:hidden;background:#d8d3ca;isolation:isolate}
.cc-stage img{object-fit:cover}
.cc-stage .badge{top:14px;z-index:2;padding:5px 11px;background:rgba(15,17,21,.78);font-size:.72rem;letter-spacing:.08em;text-transform:uppercase}
.cc-tag-b{left:14px}.cc-tag-a{right:14px}
.cc-note{font-size:.88rem;color:#5f5a52}

/* 1. Radio buttons (.btn-check) and :has(). Each radio sets --v; the clip and divider follow with a transition. */
.rc{--v:50}
.rc:has([value="before"]:checked){--v:100}
.rc:has([value="after"]:checked){--v:0}
.rc .cc-top{clip-path:inset(0 calc(100% - var(--v) * 1%) 0 0);transition:clip-path .55s cubic-bezier(.3,.7,.2,1)}
.rc .cc-top img{filter:brightness(1.08) contrast(.85) saturate(.2)}
.rc .cc-line{left:calc(var(--v) * 1%);width:3px;translate:-50% 0;background:#fff;box-shadow:0 0 10px rgba(0,0,0,.35);transition:left .55s cubic-bezier(.3,.7,.2,1)}
.rc-legend{font-size:.92rem}
.rc-seg{padding:4px;border-radius:999px;background:#e4e0d8}
.rc-seg .btn{--bs-btn-padding-x:16px;--bs-btn-padding-y:8px;--bs-btn-font-size:.85rem;--bs-btn-font-weight:600;--bs-btn-color:#3d3a35;
  --bs-btn-border-width:0;--bs-btn-active-bg:#16181d;--bs-btn-active-color:#fff;--bs-btn-hover-color:#16181d;--bs-btn-focus-box-shadow:0 0 0 3px #1d4ed8;
  border-radius:999px!important;line-height:normal;transition:background .2s,color .2s}

/* 2. resize: horizontal. Mouse and trackpad only: not reachable by keyboard, not supported on most touch screens. */
.rz{container-type:inline-size}
.rz-before{position:absolute;top:0;left:0;bottom:0;z-index:1;width:50%;min-width:40px;max-width:100%;overflow:hidden;resize:horizontal;border-right:3px solid #fff}
.rz-before img{position:absolute;top:0;left:0;width:100cqw;max-width:none;height:100%;filter:brightness(.95) contrast(.85) saturate(.6) sepia(.4)}
.rz-grip{place-items:center;right:0;bottom:0;width:30px;height:30px;border-radius:8px 0 0 0;color:#16181d}
.rz-grip svg{width:16px;height:16px}
@media (prefers-reduced-motion:reduce){.rc .cc-top,.rc .cc-line,.rc-seg .btn{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5f5a52] uppercase">Radio buttons and :has(), no JavaScript</span>
<figure class="[--v:50] has-[[value=after]:checked]:[--v:0] has-[[value=before]:checked]:[--v:100]">
  <div class="relative isolate aspect-[3/2] overflow-hidden rounded-2xl bg-[#d8d3ca]">
    <img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" class="absolute inset-0 block size-full object-cover" loading="lazy" alt="After: a wooden jetty stretching into a deep blue lake lined with green forest, in full colour">
    <div class="absolute inset-0 [clip-path:inset(0_calc(100%_-_var(--v)*1%)_0_0)] transition-[clip-path] duration-[550ms] ease-[cubic-bezier(.3,.7,.2,1)] motion-reduce:transition-none"><img src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" class="block size-full object-cover brightness-108 contrast-85 saturate-20" loading="lazy" alt="Before: the same jetty and lake almost colourless and flat"></div>
    <span class="absolute inset-y-0 left-[calc(var(--v)*1%)] w-[3px] -translate-x-1/2 bg-white shadow-[0_0_10px_rgba(0,0,0,.35)] transition-[left] duration-[550ms] ease-[cubic-bezier(.3,.7,.2,1)] motion-reduce:transition-none" aria-hidden="true"></span>
    <span class="pointer-events-none absolute top-3.5 left-3.5 z-2 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase" aria-hidden="true">Before</span><span class="pointer-events-none absolute top-3.5 right-3.5 z-2 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase" aria-hidden="true">After</span>
  </div>
  <fieldset class="mt-3.5 flex flex-wrap items-center justify-between gap-3">
    <legend class="float-left text-[.92rem] font-semibold">Lake jetty, Ontario</legend>
    <span class="inline-flex rounded-full bg-[#e4e0d8] p-1">
      <label class="relative cursor-pointer"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="rct-view" value="before"><span class="block rounded-full px-4 py-2 text-[.85rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 peer-checked:bg-[#16181d] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8] motion-reduce:transition-none">Before</span></label>
      <label class="relative cursor-pointer"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="rct-view" value="split" checked><span class="block rounded-full px-4 py-2 text-[.85rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 peer-checked:bg-[#16181d] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8] motion-reduce:transition-none">Split</span></label>
      <label class="relative cursor-pointer"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="rct-view" value="after"><span class="block rounded-full px-4 py-2 text-[.85rem] font-semibold text-[#3d3a35] transition-[background-color,color] duration-200 peer-checked:bg-[#16181d] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8] motion-reduce:transition-none">After</span></label>
    </span>
  </fieldset>
</figure></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5f5a52] uppercase">resize: horizontal, drag the corner grip</span>
<figure>
  <div class="@container relative isolate aspect-[3/2] overflow-hidden rounded-2xl bg-[#d8d3ca]">
    <img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" class="absolute inset-0 block size-full object-cover" loading="lazy" alt="After: a pine forest and a wide glacial valley below snowy mountains under a bright blue sky">
    <div class="absolute inset-y-0 left-0 z-1 w-1/2 max-w-full min-w-10 resize-x overflow-hidden border-r-3 border-white"><img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70" srcset="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&h=400&q=70 600w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&h=600&q=70 900w, https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&h=800&q=70 1200w" sizes="(min-width: 1040px) 980px, calc(100vw - 32px)" width="1200" height="800" class="absolute top-0 left-0 block h-full w-[100cqw] max-w-none object-cover brightness-95 contrast-85 saturate-60 sepia-40" loading="lazy" alt="Before: the same valley in dull, faded colour"><span class="pointer-events-none absolute right-0 bottom-0 grid size-[30px] place-items-center rounded-tl-lg bg-white text-[#16181d]" aria-hidden="true"><svg class="size-4" viewBox="0 0 16 16"><path d="M14 6L6 14M14 10l-4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span></div>
    <span class="pointer-events-none absolute top-3.5 left-3.5 z-2 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase" aria-hidden="true">Before</span><span class="pointer-events-none absolute top-3.5 right-3.5 z-2 rounded-full bg-[rgba(15,17,21,.78)] px-[11px] py-[5px] text-[.72rem] font-bold tracking-[.08em] text-white uppercase" aria-hidden="true">After</span>
  </div>
  <figcaption class="mt-3 text-[.88rem] text-[#5f5a52]"><strong class="font-semibold text-[#16181d]">Kluane valley.</strong> Mouse and trackpad only: resize has no keyboard or touch support, so use it for internal tools or pair it with the radio version above.</figcaption>
</figure></div>

About this set

Two before and after comparisons with no JavaScript at all, and an honest note on what each one can and cannot do. Radio buttons and :has() puts a segmented control of three real radio buttons, Before, Split and After, under a lake photo. A rule such as .rc:has([value=before]:checked) sets –v on the figure, and the clip-path and divider animate to the new position. It cannot be dragged to an arbitrary point, but it is fully keyboard accessible: Tab reaches the group and the arrow keys change the view, and it is announced as a radio group. resize: horizontal uses the browser’s own resize grip: the before layer is a box with resize: horizontal and overflow: hidden, its photo is sized with 100cqw so it stays aligned while the box changes width, and a small white tab marks the grip in the corner. It drags smoothly with a mouse or trackpad, but resize has no keyboard support and does not work on most touch screens, so it suits internal tools and prototypes, or sits next to an accessible control. Both photos are one image with a CSS filter on the before layer, so they always line up.

What’s included

  • No JavaScript in either version
  • Radio buttons and :has() switch between before, split and after
  • Animated clip-path transition between the three states
  • resize: horizontal drag with the photo kept aligned through container query units
  • Limits of each technique stated in the demo caption

Accessibility

The radio version is a fieldset with a legend, so the three options are announced as a group and change with the arrow keys; the visible pill shows a blue outline on keyboard focus and the transition is switched off under prefers-reduced-motion. The resize version cannot be reached by keyboard or touch, which the caption says, so pair it with the radio control or the range-based sets. All photos have alt text naming the version.

Customise it

Add or change radio values and the matching :has() rules to set more positions on --v, and edit the cc-top filter for the before look. In Bootstrap the radios are .btn-check with .btn restyled through --bs-btn-* variables; in Tailwind edit the has-[[value=before]:checked]:[--v:100] classes on the figure.