Open demo

CSS code

HTML
<div><span class="lbl">Thumbnails under the photo</span>
<section class="tv" aria-labelledby="tv-title">
  <div class="gh"><div><p class="gh-kicker">Concrete & Glass</p><h2 id="tv-title">Architecture walk</h2></div><p class="gh-meta">5 photographs · Ravi Menon</p></div>
  <div class="tv-view">
    <input class="tv-radio" type="radio" name="tva" id="tva1" checked aria-label="Photo 1 of 5: Museum wing">
    <input class="tv-radio" type="radio" name="tva" id="tva2" aria-label="Photo 2 of 5: Stacked flats">
    <input class="tv-radio" type="radio" name="tva" id="tva3" aria-label="Photo 3 of 5: Night skyline">
    <input class="tv-radio" type="radio" name="tva" id="tva4" aria-label="Photo 4 of 5: Mirror sculpture">
    <input class="tv-radio" type="radio" name="tva" id="tva5" aria-label="Photo 5 of 5: Downtown from above">
    <div class="tv-stage">
      <figure class="tv-slide"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="A white angular building of glass and steel panels under a pale sky"><figcaption><strong>Museum wing</strong><span>East facade, 10:30</span></figcaption></figure>
      <figure class="tv-slide"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="A white apartment block of stacked, offset boxes against a pale blue sky"><figcaption><strong>Stacked flats</strong><span>Courtyard side, 12:15</span></figcaption></figure>
      <figure class="tv-slide"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="A city skyline at night with a tall needle-like tower among lit office blocks"><figcaption><strong>Night skyline</strong><span>From the harbour, 21:40</span></figcaption></figure>
      <figure class="tv-slide"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="A polished mirror-like sculpture in a plaza, reflecting the skyscrapers around it"><figcaption><strong>Mirror sculpture</strong><span>Plaza level, 15:05</span></figcaption></figure>
      <figure class="tv-slide"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="An aerial view of dense downtown towers running to the edge of a lake"><figcaption><strong>Downtown from above</strong><span>Observation deck, 18:20</span></figcaption></figure>
    </div>
    <div class="tv-thumbs">
      <label for="tva1"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label>
      <label for="tva2"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label>
      <label for="tva3"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label>
      <label for="tva4"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label>
      <label for="tva5"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label>
    </div>
  </div>
</section></div>

<div><span class="lbl">Contact sheet beside the photo</span>
<section class="cs" aria-labelledby="cs-title">
  <div class="gh"><div><p class="gh-kicker">Salt & Light</p><h2 id="cs-title">Coast diary</h2></div><p class="gh-meta">4 frames · Noor Haddad</p></div>
  <div class="cs-view">
    <input class="cs-radio" type="radio" name="csa" id="csa1" checked aria-label="Photo 1 of 4: Low sun, high tide">
    <input class="cs-radio" type="radio" name="csa" id="csa2" aria-label="Photo 2 of 4: Lights across the bay">
    <input class="cs-radio" type="radio" name="csa" id="csa3" aria-label="Photo 3 of 4: Gulls at the waterline">
    <input class="cs-radio" type="radio" name="csa" id="csa4" aria-label="Photo 4 of 4: Swell at dusk">
    <div class="cs-stage">
      <figure class="cs-slide"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=1200&h=900&q=70" width="1200" height="900" alt="Waves running up an empty sandy beach at sunset"><figcaption>Frame 01 · Low sun, high tide</figcaption></figure>
      <figure class="cs-slide"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=1200&h=900&q=70" width="1200" height="900" loading="lazy" alt="Pink and blue sunset over a calm sea, with dark rocks in front and town lights on the far shore"><figcaption>Frame 02 · Lights across the bay</figcaption></figure>
      <figure class="cs-slide"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=1200&h=900&q=70" width="1200" height="900" loading="lazy" alt="Gulls flying over a wet beach as the sun sits low over the sea"><figcaption>Frame 03 · Gulls at the waterline</figcaption></figure>
      <figure class="cs-slide"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=1200&h=900&q=70" width="1200" height="900" loading="lazy" alt="Green sea waves under a cloudy sky with the sun setting on the horizon"><figcaption>Frame 04 · Swell at dusk</figcaption></figure>
    </div>
    <div class="cs-thumbs">
      <label for="csa1"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=240&h=180&q=70" width="120" height="90" alt=""><span aria-hidden="true">01</span></label>
      <label for="csa2"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=240&h=180&q=70" width="120" height="90" alt=""><span aria-hidden="true">02</span></label>
      <label for="csa3"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=240&h=180&q=70" width="120" height="90" alt=""><span aria-hidden="true">03</span></label>
      <label for="csa4"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=240&h=180&q=70" width="120" height="90" alt=""><span aria-hidden="true">04</span></label>
    </div>
  </div>
</section></div>
CSS
/* Thumbnail viewers without JavaScript: one visually hidden radio per photo comes first,
   and the sibling combinator (~) shows the matching photo and highlights the matching thumbnail.
   The thumbnails are <label>s, so a click checks the radio; Tab reaches the group and
   the arrow keys move between photos. Add one line to each list below per extra photo. */
.gh{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:6px 24px;margin-bottom:20px}
.gh-kicker{font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#2f54c4}
.gh h2{margin-top:4px;font-size:1.75rem;line-height:1.15;letter-spacing:-.02em}
.gh-meta{font-size:.9rem;color:#566072}
.tv-radio,.cs-radio{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* 1. Thumbnails under the photo */
.tv{--tv-accent:#2f54c4;--tv-radius:16px}
.tv-view{position:relative}
.tv-stage{display:grid;border-radius:var(--tv-radius);overflow:hidden;background:#d5dae3}
.tv-slide{grid-area:1/1;position:relative;margin:0;opacity:0;visibility:hidden;transition:opacity .4s,visibility .4s}
.tv-slide img{width:100%;height:auto;aspect-ratio:16/10;object-fit:cover;display:block}
.tv-slide figcaption{position:absolute;left:16px;bottom:16px;display:flex;flex-direction:column;padding:10px 14px;border-radius:10px;background:rgba(14,18,28,.78);color:#fff}
.tv-slide strong{font-size:1rem}
.tv-slide figcaption span{font-size:.8rem;color:#dfe4ee}
.tv-thumbs{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:10px}
.tv-thumbs label{position:relative;display:block;border-radius:10px;overflow:hidden;cursor:pointer;background:#d5dae3}
.tv-thumbs img{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;display:block;opacity:.55;transition:opacity .2s}
.tv-thumbs label::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 3px transparent;transition:box-shadow .2s}
.tv-thumbs label:hover img{opacity:.85}
.tv-radio:nth-of-type(1):checked ~ .tv-stage .tv-slide:nth-child(1),
.tv-radio:nth-of-type(2):checked ~ .tv-stage .tv-slide:nth-child(2),
.tv-radio:nth-of-type(3):checked ~ .tv-stage .tv-slide:nth-child(3),
.tv-radio:nth-of-type(4):checked ~ .tv-stage .tv-slide:nth-child(4),
.tv-radio:nth-of-type(5):checked ~ .tv-stage .tv-slide:nth-child(5){opacity:1;visibility:visible}
.tv-radio:nth-of-type(1):checked ~ .tv-thumbs label:nth-child(1) img,
.tv-radio:nth-of-type(2):checked ~ .tv-thumbs label:nth-child(2) img,
.tv-radio:nth-of-type(3):checked ~ .tv-thumbs label:nth-child(3) img,
.tv-radio:nth-of-type(4):checked ~ .tv-thumbs label:nth-child(4) img,
.tv-radio:nth-of-type(5):checked ~ .tv-thumbs label:nth-child(5) img{opacity:1}
.tv-radio:nth-of-type(1):checked ~ .tv-thumbs label:nth-child(1)::after,
.tv-radio:nth-of-type(2):checked ~ .tv-thumbs label:nth-child(2)::after,
.tv-radio:nth-of-type(3):checked ~ .tv-thumbs label:nth-child(3)::after,
.tv-radio:nth-of-type(4):checked ~ .tv-thumbs label:nth-child(4)::after,
.tv-radio:nth-of-type(5):checked ~ .tv-thumbs label:nth-child(5)::after{box-shadow:inset 0 0 0 3px var(--tv-accent)}
.tv-radio:nth-of-type(1):focus-visible ~ .tv-thumbs label:nth-child(1),
.tv-radio:nth-of-type(2):focus-visible ~ .tv-thumbs label:nth-child(2),
.tv-radio:nth-of-type(3):focus-visible ~ .tv-thumbs label:nth-child(3),
.tv-radio:nth-of-type(4):focus-visible ~ .tv-thumbs label:nth-child(4),
.tv-radio:nth-of-type(5):focus-visible ~ .tv-thumbs label:nth-child(5){outline:3px solid var(--tv-accent);outline-offset:3px}
@media (max-width:640px){.tv-thumbs{gap:6px}.tv-thumbs label{border-radius:7px}.tv-slide figcaption{left:10px;bottom:10px;padding:7px 10px}}

/* 2. Contact sheet beside the photo */
.cs{--cs-accent:#c2410c}
.cs-view{position:relative;display:grid;grid-template-columns:132px 1fr;gap:14px}
.cs-stage{grid-column:2;grid-row:1;display:grid;min-width:0;padding:12px 12px 0;border-radius:6px;background:#161b26}
.cs-slide{grid-area:1/1;margin:0;opacity:0;visibility:hidden;transition:opacity .4s,visibility .4s}
.cs-slide img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;display:block}
.cs-slide figcaption{padding:10px 2px 12px;font-size:.8rem;letter-spacing:.06em;text-transform:uppercase;color:#d7dbe3}
.cs-thumbs{grid-column:1;grid-row:1;display:flex;flex-direction:column;gap:10px}
.cs-thumbs label{position:relative;display:block;padding:6px 6px 24px;border-radius:4px;background:#161b26;cursor:pointer}
.cs-thumbs img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;display:block;filter:grayscale(1);opacity:.7;transition:filter .2s,opacity .2s}
.cs-thumbs span{position:absolute;left:8px;bottom:5px;font:600 .72rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#c7ccd6}
.cs-thumbs label:hover img{filter:grayscale(.3);opacity:1}
.cs-radio:nth-of-type(1):checked ~ .cs-stage .cs-slide:nth-child(1),
.cs-radio:nth-of-type(2):checked ~ .cs-stage .cs-slide:nth-child(2),
.cs-radio:nth-of-type(3):checked ~ .cs-stage .cs-slide:nth-child(3),
.cs-radio:nth-of-type(4):checked ~ .cs-stage .cs-slide:nth-child(4){opacity:1;visibility:visible}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1),
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2),
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3),
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4){background:var(--cs-accent)}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1) img,
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2) img,
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3) img,
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4) img{filter:none;opacity:1}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1) span,
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2) span,
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3) span,
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4) span{color:#fff}
.cs-radio:nth-of-type(1):focus-visible ~ .cs-thumbs label:nth-child(1),
.cs-radio:nth-of-type(2):focus-visible ~ .cs-thumbs label:nth-child(2),
.cs-radio:nth-of-type(3):focus-visible ~ .cs-thumbs label:nth-child(3),
.cs-radio:nth-of-type(4):focus-visible ~ .cs-thumbs label:nth-child(4){outline:3px solid var(--cs-accent);outline-offset:3px}
@media (max-width:640px){.cs-view{grid-template-columns:1fr}.cs-stage{grid-column:1}.cs-thumbs{grid-row:2;flex-direction:row}.cs-thumbs label{flex:1;padding:4px 4px 20px}}
@media (prefers-reduced-motion:reduce){.tv-slide,.tv-thumbs img,.tv-thumbs label::after,.cs-slide,.cs-thumbs img{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Thumbnails under the photo</span>
<section class="tv" aria-labelledby="tv-title">
  <div class="gh d-flex flex-wrap align-items-end justify-content-between"><div><p class="gh-kicker">Concrete & Glass</p><h2 id="tv-title" class="gh-title">Architecture walk</h2></div><p class="gh-meta">5 photographs · Ravi Menon</p></div>
  <div class="tv-view position-relative">
    <input class="btn-check tv-radio" type="radio" name="tva" id="tva1" checked aria-label="Photo 1 of 5: Museum wing">
    <input class="btn-check tv-radio" type="radio" name="tva" id="tva2" aria-label="Photo 2 of 5: Stacked flats">
    <input class="btn-check tv-radio" type="radio" name="tva" id="tva3" aria-label="Photo 3 of 5: Night skyline">
    <input class="btn-check tv-radio" type="radio" name="tva" id="tva4" aria-label="Photo 4 of 5: Mirror sculpture">
    <input class="btn-check tv-radio" type="radio" name="tva" id="tva5" aria-label="Photo 5 of 5: Downtown from above">
    <div class="tv-stage ratio">
      <figure class="tv-slide m-0"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=1400&h=875&q=70" class="w-100 h-100 object-fit-cover" width="1400" height="875" alt="A white angular building of glass and steel panels under a pale sky"><figcaption><strong>Museum wing</strong><span>East facade, 10:30</span></figcaption></figure>
      <figure class="tv-slide m-0"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=1400&h=875&q=70" class="w-100 h-100 object-fit-cover" width="1400" height="875" loading="lazy" alt="A white apartment block of stacked, offset boxes against a pale blue sky"><figcaption><strong>Stacked flats</strong><span>Courtyard side, 12:15</span></figcaption></figure>
      <figure class="tv-slide m-0"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=1400&h=875&q=70" class="w-100 h-100 object-fit-cover" width="1400" height="875" loading="lazy" alt="A city skyline at night with a tall needle-like tower among lit office blocks"><figcaption><strong>Night skyline</strong><span>From the harbour, 21:40</span></figcaption></figure>
      <figure class="tv-slide m-0"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=1400&h=875&q=70" class="w-100 h-100 object-fit-cover" width="1400" height="875" loading="lazy" alt="A polished mirror-like sculpture in a plaza, reflecting the skyscrapers around it"><figcaption><strong>Mirror sculpture</strong><span>Plaza level, 15:05</span></figcaption></figure>
      <figure class="tv-slide m-0"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1400&h=875&q=70" class="w-100 h-100 object-fit-cover" width="1400" height="875" loading="lazy" alt="An aerial view of dense downtown towers running to the edge of a lake"><figcaption><strong>Downtown from above</strong><span>Observation deck, 18:20</span></figcaption></figure>
    </div>
    <div class="tv-thumbs row row-cols-5">
      <div class="col"><label for="tva1"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label></div>
      <div class="col"><label for="tva2"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label></div>
      <div class="col"><label for="tva3"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label></div>
      <div class="col"><label for="tva4"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label></div>
      <div class="col"><label for="tva5"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=240&h=160&q=70" width="120" height="80" alt=""></label></div>
    </div>
  </div>
</section></div>

<div><span class="lbl">Contact sheet beside the photo</span>
<section class="cs" aria-labelledby="cs-title">
  <div class="gh d-flex flex-wrap align-items-end justify-content-between"><div><p class="gh-kicker">Salt & Light</p><h2 id="cs-title" class="gh-title">Coast diary</h2></div><p class="gh-meta">4 frames · Noor Haddad</p></div>
  <div class="cs-view position-relative">
    <input class="btn-check cs-radio" type="radio" name="csa" id="csa1" checked aria-label="Photo 1 of 4: Low sun, high tide">
    <input class="btn-check cs-radio" type="radio" name="csa" id="csa2" aria-label="Photo 2 of 4: Lights across the bay">
    <input class="btn-check cs-radio" type="radio" name="csa" id="csa3" aria-label="Photo 3 of 4: Gulls at the waterline">
    <input class="btn-check cs-radio" type="radio" name="csa" id="csa4" aria-label="Photo 4 of 4: Swell at dusk">
    <div class="cs-stage">
      <figure class="cs-slide m-0"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=1200&h=900&q=70" class="object-fit-cover" width="1200" height="900" alt="Waves running up an empty sandy beach at sunset"></span><figcaption>Frame 01 · Low sun, high tide</figcaption></figure>
      <figure class="cs-slide m-0"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=1200&h=900&q=70" class="object-fit-cover" width="1200" height="900" loading="lazy" alt="Pink and blue sunset over a calm sea, with dark rocks in front and town lights on the far shore"></span><figcaption>Frame 02 · Lights across the bay</figcaption></figure>
      <figure class="cs-slide m-0"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=1200&h=900&q=70" class="object-fit-cover" width="1200" height="900" loading="lazy" alt="Gulls flying over a wet beach as the sun sits low over the sea"></span><figcaption>Frame 03 · Gulls at the waterline</figcaption></figure>
      <figure class="cs-slide m-0"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=1200&h=900&q=70" class="object-fit-cover" width="1200" height="900" loading="lazy" alt="Green sea waves under a cloudy sky with the sun setting on the horizon"></span><figcaption>Frame 04 · Swell at dusk</figcaption></figure>
    </div>
    <div class="cs-thumbs">
      <label for="csa1"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=240&h=180&q=70" class="object-fit-cover" width="120" height="90" alt=""></span><span class="cs-num" aria-hidden="true">01</span></label>
      <label for="csa2"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=240&h=180&q=70" class="object-fit-cover" width="120" height="90" alt=""></span><span class="cs-num" aria-hidden="true">02</span></label>
      <label for="csa3"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=240&h=180&q=70" class="object-fit-cover" width="120" height="90" alt=""></span><span class="cs-num" aria-hidden="true">03</span></label>
      <label for="csa4"><span class="ratio ratio-4x3 d-block"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=240&h=180&q=70" class="object-fit-cover" width="120" height="90" alt=""></span><span class="cs-num" aria-hidden="true">04</span></label>
    </div>
  </div>
</section></div>
CSS
/* Thumbnail viewers without JavaScript, on Bootstrap: the radios use .btn-check (visually hidden),
   the photo sits in a .ratio box and the strip is a .row-cols-5 grid. The sibling combinator (~)
   shows the photo whose radio is checked. Add one line to each list per extra photo. */
.gh{gap:6px 24px;margin-bottom:20px}
.gh-kicker{margin:0;font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#2f54c4}
.gh-title{margin:4px 0 0;font-size:1.75rem;font-weight:700;line-height:1.15;letter-spacing:-.02em}
.gh-meta{margin:0;font-size:.9rem;color:#566072}

/* 1. Thumbnails under the photo */
.tv{--tv-accent:#2f54c4;--tv-radius:16px}
.tv-stage{--bs-aspect-ratio:62.5%;border-radius:var(--tv-radius);overflow:hidden;background:#d5dae3}
.tv-slide{opacity:0;visibility:hidden;transition:opacity .4s,visibility .4s}
.tv-slide figcaption{position:absolute;left:16px;bottom:16px;display:flex;flex-direction:column;padding:10px 14px;border-radius:10px;background:rgba(14,18,28,.78);color:#fff}
.tv-slide strong{font-size:1rem}
.tv-slide figcaption span{font-size:.8rem;color:#dfe4ee}
.tv-thumbs{--bs-gutter-x:10px;--bs-gutter-y:0;margin-top:10px}
.tv-thumbs label{position:relative;display:block;border-radius:10px;overflow:hidden;cursor:pointer;background:#d5dae3}
.tv-thumbs img{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;display:block;opacity:.55;transition:opacity .2s}
.tv-thumbs label::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 3px transparent;transition:box-shadow .2s}
.tv-thumbs label:hover img{opacity:.85}
.tv-radio:nth-of-type(1):checked ~ .tv-stage .tv-slide:nth-child(1),
.tv-radio:nth-of-type(2):checked ~ .tv-stage .tv-slide:nth-child(2),
.tv-radio:nth-of-type(3):checked ~ .tv-stage .tv-slide:nth-child(3),
.tv-radio:nth-of-type(4):checked ~ .tv-stage .tv-slide:nth-child(4),
.tv-radio:nth-of-type(5):checked ~ .tv-stage .tv-slide:nth-child(5){opacity:1;visibility:visible}
.tv-radio:nth-of-type(1):checked ~ .tv-thumbs .col:nth-child(1) img,
.tv-radio:nth-of-type(2):checked ~ .tv-thumbs .col:nth-child(2) img,
.tv-radio:nth-of-type(3):checked ~ .tv-thumbs .col:nth-child(3) img,
.tv-radio:nth-of-type(4):checked ~ .tv-thumbs .col:nth-child(4) img,
.tv-radio:nth-of-type(5):checked ~ .tv-thumbs .col:nth-child(5) img{opacity:1}
.tv-radio:nth-of-type(1):checked ~ .tv-thumbs .col:nth-child(1) label::after,
.tv-radio:nth-of-type(2):checked ~ .tv-thumbs .col:nth-child(2) label::after,
.tv-radio:nth-of-type(3):checked ~ .tv-thumbs .col:nth-child(3) label::after,
.tv-radio:nth-of-type(4):checked ~ .tv-thumbs .col:nth-child(4) label::after,
.tv-radio:nth-of-type(5):checked ~ .tv-thumbs .col:nth-child(5) label::after{box-shadow:inset 0 0 0 3px var(--tv-accent)}
.tv-radio:nth-of-type(1):focus-visible ~ .tv-thumbs .col:nth-child(1) label,
.tv-radio:nth-of-type(2):focus-visible ~ .tv-thumbs .col:nth-child(2) label,
.tv-radio:nth-of-type(3):focus-visible ~ .tv-thumbs .col:nth-child(3) label,
.tv-radio:nth-of-type(4):focus-visible ~ .tv-thumbs .col:nth-child(4) label,
.tv-radio:nth-of-type(5):focus-visible ~ .tv-thumbs .col:nth-child(5) label{outline:3px solid var(--tv-accent);outline-offset:3px}
@media (max-width:640px){.tv-thumbs{--bs-gutter-x:6px}.tv-thumbs label{border-radius:7px}.tv-slide figcaption{left:10px;bottom:10px;padding:7px 10px}}

/* 2. Contact sheet beside the photo */
.cs{--cs-accent:#c2410c}
.cs-view{display:grid;grid-template-columns:132px 1fr;gap:14px}
.cs-stage{grid-column:2;grid-row:1;display:grid;min-width:0;padding:12px 12px 0;border-radius:6px;background:#161b26}
.cs-slide{grid-area:1/1;opacity:0;visibility:hidden;transition:opacity .4s,visibility .4s}
.cs-slide figcaption{padding:10px 2px 12px;font-size:.8rem;letter-spacing:.06em;text-transform:uppercase;color:#d7dbe3}
.cs-thumbs{grid-column:1;grid-row:1;display:flex;flex-direction:column;gap:10px}
.cs-thumbs label{position:relative;display:block;padding:6px 6px 24px;border-radius:4px;background:#161b26;cursor:pointer}
.cs-thumbs img{filter:grayscale(1);opacity:.7;transition:filter .2s,opacity .2s}
.cs-num{position:absolute;left:8px;bottom:5px;font:600 .72rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#c7ccd6}
.cs-thumbs label:hover img{filter:grayscale(.3);opacity:1}
.cs-radio:nth-of-type(1):checked ~ .cs-stage .cs-slide:nth-child(1),
.cs-radio:nth-of-type(2):checked ~ .cs-stage .cs-slide:nth-child(2),
.cs-radio:nth-of-type(3):checked ~ .cs-stage .cs-slide:nth-child(3),
.cs-radio:nth-of-type(4):checked ~ .cs-stage .cs-slide:nth-child(4){opacity:1;visibility:visible}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1),
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2),
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3),
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4){background:var(--cs-accent)}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1) img,
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2) img,
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3) img,
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4) img{filter:none;opacity:1}
.cs-radio:nth-of-type(1):checked ~ .cs-thumbs label:nth-child(1) .cs-num,
.cs-radio:nth-of-type(2):checked ~ .cs-thumbs label:nth-child(2) .cs-num,
.cs-radio:nth-of-type(3):checked ~ .cs-thumbs label:nth-child(3) .cs-num,
.cs-radio:nth-of-type(4):checked ~ .cs-thumbs label:nth-child(4) .cs-num{color:#fff}
.cs-radio:nth-of-type(1):focus-visible ~ .cs-thumbs label:nth-child(1),
.cs-radio:nth-of-type(2):focus-visible ~ .cs-thumbs label:nth-child(2),
.cs-radio:nth-of-type(3):focus-visible ~ .cs-thumbs label:nth-child(3),
.cs-radio:nth-of-type(4):focus-visible ~ .cs-thumbs label:nth-child(4){outline:3px solid var(--cs-accent);outline-offset:3px}
@media (max-width:640px){.cs-view{grid-template-columns:1fr}.cs-stage{grid-column:1}.cs-thumbs{grid-row:2;flex-direction:row}.cs-thumbs label{flex:1;padding:4px 4px 20px}}
@media (prefers-reduced-motion:reduce){.tv-slide,.tv-thumbs img,.tv-thumbs label::after,.cs-slide,.cs-thumbs img{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#566072] uppercase">Thumbnails under the photo</span>
<section aria-labelledby="tv-title">
  <div class="mb-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-1.5"><div><p class="text-xs leading-[normal] font-bold tracking-[.14em] text-[#2f54c4] uppercase">Concrete & Glass</p><h2 id="tv-title" class="mt-1 text-[1.75rem] leading-[1.15] font-bold tracking-[-.02em]">Architecture walk</h2></div><p class="text-[.9rem] leading-[normal] text-[#566072]">5 photographs · Ravi Menon</p></div>
  <div class="relative">
    <input class="sr-only" type="radio" name="tva" id="tva1" checked aria-label="Photo 1 of 5: Museum wing">
    <input class="sr-only" type="radio" name="tva" id="tva2" aria-label="Photo 2 of 5: Stacked flats">
    <input class="sr-only" type="radio" name="tva" id="tva3" aria-label="Photo 3 of 5: Night skyline">
    <input class="sr-only" type="radio" name="tva" id="tva4" aria-label="Photo 4 of 5: Mirror sculpture">
    <input class="sr-only" type="radio" name="tva" id="tva5" aria-label="Photo 5 of 5: Downtown from above">
    <div class="grid overflow-hidden rounded-2xl bg-[#d5dae3]">
      <figure class="invisible relative col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#tva1:checked~*_&]:visible [#tva1:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=1400&h=875&q=70" class="block aspect-[16/10] h-auto w-full object-cover" width="1400" height="875" alt="A white angular building of glass and steel panels under a pale sky"><figcaption class="absolute bottom-2.5 left-2.5 flex flex-col rounded-[10px] bg-[rgba(14,18,28,.78)] px-2.5 py-[7px] text-white sm:bottom-4 sm:left-4 sm:px-3.5 sm:py-2.5"><strong class="text-base">Museum wing</strong><span class="text-[.8rem] text-[#dfe4ee]">East facade, 10:30</span></figcaption></figure>
      <figure class="invisible relative col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#tva2:checked~*_&]:visible [#tva2:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=1400&h=875&q=70" class="block aspect-[16/10] h-auto w-full object-cover" width="1400" height="875" loading="lazy" alt="A white apartment block of stacked, offset boxes against a pale blue sky"><figcaption class="absolute bottom-2.5 left-2.5 flex flex-col rounded-[10px] bg-[rgba(14,18,28,.78)] px-2.5 py-[7px] text-white sm:bottom-4 sm:left-4 sm:px-3.5 sm:py-2.5"><strong class="text-base">Stacked flats</strong><span class="text-[.8rem] text-[#dfe4ee]">Courtyard side, 12:15</span></figcaption></figure>
      <figure class="invisible relative col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#tva3:checked~*_&]:visible [#tva3:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=1400&h=875&q=70" class="block aspect-[16/10] h-auto w-full object-cover" width="1400" height="875" loading="lazy" alt="A city skyline at night with a tall needle-like tower among lit office blocks"><figcaption class="absolute bottom-2.5 left-2.5 flex flex-col rounded-[10px] bg-[rgba(14,18,28,.78)] px-2.5 py-[7px] text-white sm:bottom-4 sm:left-4 sm:px-3.5 sm:py-2.5"><strong class="text-base">Night skyline</strong><span class="text-[.8rem] text-[#dfe4ee]">From the harbour, 21:40</span></figcaption></figure>
      <figure class="invisible relative col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#tva4:checked~*_&]:visible [#tva4:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=1400&h=875&q=70" class="block aspect-[16/10] h-auto w-full object-cover" width="1400" height="875" loading="lazy" alt="A polished mirror-like sculpture in a plaza, reflecting the skyscrapers around it"><figcaption class="absolute bottom-2.5 left-2.5 flex flex-col rounded-[10px] bg-[rgba(14,18,28,.78)] px-2.5 py-[7px] text-white sm:bottom-4 sm:left-4 sm:px-3.5 sm:py-2.5"><strong class="text-base">Mirror sculpture</strong><span class="text-[.8rem] text-[#dfe4ee]">Plaza level, 15:05</span></figcaption></figure>
      <figure class="invisible relative col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#tva5:checked~*_&]:visible [#tva5:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1400&h=875&q=70" class="block aspect-[16/10] h-auto w-full object-cover" width="1400" height="875" loading="lazy" alt="An aerial view of dense downtown towers running to the edge of a lake"><figcaption class="absolute bottom-2.5 left-2.5 flex flex-col rounded-[10px] bg-[rgba(14,18,28,.78)] px-2.5 py-[7px] text-white sm:bottom-4 sm:left-4 sm:px-3.5 sm:py-2.5"><strong class="text-base">Downtown from above</strong><span class="text-[.8rem] text-[#dfe4ee]">Observation deck, 18:20</span></figcaption></figure>
    </div>
    <div class="mt-2.5 grid grid-cols-5 gap-1.5 sm:gap-2.5">
      <label for="tva1" class="group relative block cursor-pointer overflow-hidden rounded-[7px] bg-[#d5dae3] after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_3px_transparent] after:transition-shadow after:duration-200 sm:rounded-[10px] motion-reduce:after:transition-none [#tva1:checked~*_&]:after:shadow-[inset_0_0_0_3px_#2f54c4] [#tva1:focus-visible~*_&]:outline-3 [#tva1:focus-visible~*_&]:outline-offset-3 [#tva1:focus-visible~*_&]:outline-[#2f54c4]"><img src="https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&w=240&h=160&q=70" class="block aspect-[3/2] h-auto w-full object-cover opacity-55 transition-opacity duration-200 group-hover:opacity-85 motion-reduce:transition-none [#tva1:checked~*_&]:opacity-100" width="120" height="80" alt=""></label>
      <label for="tva2" class="group relative block cursor-pointer overflow-hidden rounded-[7px] bg-[#d5dae3] after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_3px_transparent] after:transition-shadow after:duration-200 sm:rounded-[10px] motion-reduce:after:transition-none [#tva2:checked~*_&]:after:shadow-[inset_0_0_0_3px_#2f54c4] [#tva2:focus-visible~*_&]:outline-3 [#tva2:focus-visible~*_&]:outline-offset-3 [#tva2:focus-visible~*_&]:outline-[#2f54c4]"><img src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=240&h=160&q=70" class="block aspect-[3/2] h-auto w-full object-cover opacity-55 transition-opacity duration-200 group-hover:opacity-85 motion-reduce:transition-none [#tva2:checked~*_&]:opacity-100" width="120" height="80" alt=""></label>
      <label for="tva3" class="group relative block cursor-pointer overflow-hidden rounded-[7px] bg-[#d5dae3] after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_3px_transparent] after:transition-shadow after:duration-200 sm:rounded-[10px] motion-reduce:after:transition-none [#tva3:checked~*_&]:after:shadow-[inset_0_0_0_3px_#2f54c4] [#tva3:focus-visible~*_&]:outline-3 [#tva3:focus-visible~*_&]:outline-offset-3 [#tva3:focus-visible~*_&]:outline-[#2f54c4]"><img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=240&h=160&q=70" class="block aspect-[3/2] h-auto w-full object-cover opacity-55 transition-opacity duration-200 group-hover:opacity-85 motion-reduce:transition-none [#tva3:checked~*_&]:opacity-100" width="120" height="80" alt=""></label>
      <label for="tva4" class="group relative block cursor-pointer overflow-hidden rounded-[7px] bg-[#d5dae3] after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_3px_transparent] after:transition-shadow after:duration-200 sm:rounded-[10px] motion-reduce:after:transition-none [#tva4:checked~*_&]:after:shadow-[inset_0_0_0_3px_#2f54c4] [#tva4:focus-visible~*_&]:outline-3 [#tva4:focus-visible~*_&]:outline-offset-3 [#tva4:focus-visible~*_&]:outline-[#2f54c4]"><img src="https://images.unsplash.com/photo-1494522855154-9297ac14b55f?auto=format&fit=crop&w=240&h=160&q=70" class="block aspect-[3/2] h-auto w-full object-cover opacity-55 transition-opacity duration-200 group-hover:opacity-85 motion-reduce:transition-none [#tva4:checked~*_&]:opacity-100" width="120" height="80" alt=""></label>
      <label for="tva5" class="group relative block cursor-pointer overflow-hidden rounded-[7px] bg-[#d5dae3] after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_3px_transparent] after:transition-shadow after:duration-200 sm:rounded-[10px] motion-reduce:after:transition-none [#tva5:checked~*_&]:after:shadow-[inset_0_0_0_3px_#2f54c4] [#tva5:focus-visible~*_&]:outline-3 [#tva5:focus-visible~*_&]:outline-offset-3 [#tva5:focus-visible~*_&]:outline-[#2f54c4]"><img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=240&h=160&q=70" class="block aspect-[3/2] h-auto w-full object-cover opacity-55 transition-opacity duration-200 group-hover:opacity-85 motion-reduce:transition-none [#tva5:checked~*_&]:opacity-100" width="120" height="80" alt=""></label>
    </div>
  </div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#566072] uppercase">Contact sheet beside the photo</span>
<section aria-labelledby="cs-title">
  <div class="mb-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-1.5"><div><p class="text-xs leading-[normal] font-bold tracking-[.14em] text-[#2f54c4] uppercase">Salt & Light</p><h2 id="cs-title" class="mt-1 text-[1.75rem] leading-[1.15] font-bold tracking-[-.02em]">Coast diary</h2></div><p class="text-[.9rem] leading-[normal] text-[#566072]">4 frames · Noor Haddad</p></div>
  <div class="relative grid gap-3.5 sm:grid-cols-[132px_1fr]">
    <input class="sr-only" type="radio" name="csa" id="csa1" checked aria-label="Photo 1 of 4: Low sun, high tide">
    <input class="sr-only" type="radio" name="csa" id="csa2" aria-label="Photo 2 of 4: Lights across the bay">
    <input class="sr-only" type="radio" name="csa" id="csa3" aria-label="Photo 3 of 4: Gulls at the waterline">
    <input class="sr-only" type="radio" name="csa" id="csa4" aria-label="Photo 4 of 4: Swell at dusk">
    <div class="grid min-w-0 rounded-md bg-[#161b26] px-3 pt-3 sm:col-start-2 sm:row-start-1">
      <figure class="invisible col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#csa1:checked~*_&]:visible [#csa1:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=1200&h=900&q=70" class="block aspect-[4/3] h-auto w-full object-cover" width="1200" height="900" alt="Waves running up an empty sandy beach at sunset"><figcaption class="px-0.5 pt-2.5 pb-3 text-[.8rem] tracking-[.06em] text-[#d7dbe3] uppercase">Frame 01 · Low sun, high tide</figcaption></figure>
      <figure class="invisible col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#csa2:checked~*_&]:visible [#csa2:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=1200&h=900&q=70" class="block aspect-[4/3] h-auto w-full object-cover" width="1200" height="900" loading="lazy" alt="Pink and blue sunset over a calm sea, with dark rocks in front and town lights on the far shore"><figcaption class="px-0.5 pt-2.5 pb-3 text-[.8rem] tracking-[.06em] text-[#d7dbe3] uppercase">Frame 02 · Lights across the bay</figcaption></figure>
      <figure class="invisible col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#csa3:checked~*_&]:visible [#csa3:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=1200&h=900&q=70" class="block aspect-[4/3] h-auto w-full object-cover" width="1200" height="900" loading="lazy" alt="Gulls flying over a wet beach as the sun sits low over the sea"><figcaption class="px-0.5 pt-2.5 pb-3 text-[.8rem] tracking-[.06em] text-[#d7dbe3] uppercase">Frame 03 · Gulls at the waterline</figcaption></figure>
      <figure class="invisible col-start-1 row-start-1 opacity-0 transition-[opacity,visibility] duration-400 motion-reduce:transition-none [#csa4:checked~*_&]:visible [#csa4:checked~*_&]:opacity-100"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=1200&h=900&q=70" class="block aspect-[4/3] h-auto w-full object-cover" width="1200" height="900" loading="lazy" alt="Green sea waves under a cloudy sky with the sun setting on the horizon"><figcaption class="px-0.5 pt-2.5 pb-3 text-[.8rem] tracking-[.06em] text-[#d7dbe3] uppercase">Frame 04 · Swell at dusk</figcaption></figure>
    </div>
    <div class="row-start-2 flex gap-2.5 sm:col-start-1 sm:row-start-1 sm:flex-col">
      <label for="csa1" class="group relative block flex-1 cursor-pointer rounded bg-[#161b26] px-1 pt-1 pb-5 sm:flex-none sm:px-1.5 sm:pt-1.5 sm:pb-6 [#csa1:checked~*_&]:bg-[#c2410c] [#csa1:focus-visible~*_&]:outline-3 [#csa1:focus-visible~*_&]:outline-offset-3 [#csa1:focus-visible~*_&]:outline-[#c2410c]"><img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=240&h=180&q=70" class="block aspect-[4/3] h-auto w-full object-cover opacity-70 grayscale transition-[filter,opacity] duration-200 group-hover:opacity-100 group-hover:grayscale-30 motion-reduce:transition-none [#csa1:checked~*_&]:opacity-100 [#csa1:checked~*_&]:grayscale-0" width="120" height="90" alt=""><span class="absolute bottom-[5px] left-2 font-mono text-[.72rem] leading-none font-semibold text-[#c7ccd6] [#csa1:checked~*_&]:text-white" aria-hidden="true">01</span></label>
      <label for="csa2" class="group relative block flex-1 cursor-pointer rounded bg-[#161b26] px-1 pt-1 pb-5 sm:flex-none sm:px-1.5 sm:pt-1.5 sm:pb-6 [#csa2:checked~*_&]:bg-[#c2410c] [#csa2:focus-visible~*_&]:outline-3 [#csa2:focus-visible~*_&]:outline-offset-3 [#csa2:focus-visible~*_&]:outline-[#c2410c]"><img src="https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?auto=format&fit=crop&w=240&h=180&q=70" class="block aspect-[4/3] h-auto w-full object-cover opacity-70 grayscale transition-[filter,opacity] duration-200 group-hover:opacity-100 group-hover:grayscale-30 motion-reduce:transition-none [#csa2:checked~*_&]:opacity-100 [#csa2:checked~*_&]:grayscale-0" width="120" height="90" alt=""><span class="absolute bottom-[5px] left-2 font-mono text-[.72rem] leading-none font-semibold text-[#c7ccd6] [#csa2:checked~*_&]:text-white" aria-hidden="true">02</span></label>
      <label for="csa3" class="group relative block flex-1 cursor-pointer rounded bg-[#161b26] px-1 pt-1 pb-5 sm:flex-none sm:px-1.5 sm:pt-1.5 sm:pb-6 [#csa3:checked~*_&]:bg-[#c2410c] [#csa3:focus-visible~*_&]:outline-3 [#csa3:focus-visible~*_&]:outline-offset-3 [#csa3:focus-visible~*_&]:outline-[#c2410c]"><img src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&w=240&h=180&q=70" class="block aspect-[4/3] h-auto w-full object-cover opacity-70 grayscale transition-[filter,opacity] duration-200 group-hover:opacity-100 group-hover:grayscale-30 motion-reduce:transition-none [#csa3:checked~*_&]:opacity-100 [#csa3:checked~*_&]:grayscale-0" width="120" height="90" alt=""><span class="absolute bottom-[5px] left-2 font-mono text-[.72rem] leading-none font-semibold text-[#c7ccd6] [#csa3:checked~*_&]:text-white" aria-hidden="true">03</span></label>
      <label for="csa4" class="group relative block flex-1 cursor-pointer rounded bg-[#161b26] px-1 pt-1 pb-5 sm:flex-none sm:px-1.5 sm:pt-1.5 sm:pb-6 [#csa4:checked~*_&]:bg-[#c2410c] [#csa4:focus-visible~*_&]:outline-3 [#csa4:focus-visible~*_&]:outline-offset-3 [#csa4:focus-visible~*_&]:outline-[#c2410c]"><img src="https://images.unsplash.com/photo-1439405326854-014607f694d7?auto=format&fit=crop&w=240&h=180&q=70" class="block aspect-[4/3] h-auto w-full object-cover opacity-70 grayscale transition-[filter,opacity] duration-200 group-hover:opacity-100 group-hover:grayscale-30 motion-reduce:transition-none [#csa4:checked~*_&]:opacity-100 [#csa4:checked~*_&]:grayscale-0" width="120" height="90" alt=""><span class="absolute bottom-[5px] left-2 font-mono text-[.72rem] leading-none font-semibold text-[#c7ccd6] [#csa4:checked~*_&]:text-white" aria-hidden="true">04</span></label>
    </div>
  </div>
</section></div>

About this set

Two image viewers that switch the main photo from a row of thumbnails without any JavaScript. Thumbnails under the photo shows five architecture photographs in a 16:10 frame with a caption card, and a strip of five thumbnails below; the selected one is at full strength with a blue ring and the rest are dimmed. Contact sheet beside the photo shows four coast photographs in a dark print frame with a frame number, and a column of film-style thumbnails beside it that turn from greyscale to colour when chosen and move under the photo on phones. Both work the same way: a visually hidden radio button per photo comes first in the markup, the thumbnails are its labels, and the sibling combinator shows the photo whose radio is checked, so a click, a tap or the arrow keys change the picture. Use it on product pages, property listings or anywhere a script would be overkill. The Bootstrap version uses .btn-check, .ratio and .row-cols-5, and the Tailwind version reaches each slide with arbitrary variants such as [#tva2:checked~*_&]:opacity-100.

What’s included

  • Changes the main photo with no JavaScript at all
  • Radio buttons make it keyboard operable with Tab and the arrow keys
  • Selected thumbnail is ringed and the others dimmed
  • Contact sheet layout moves its thumbnails under the photo on phones
  • Crossfade between photos that stops under reduced motion

Accessibility

Each radio button has a name such as Photo 3 of 5: Night skyline, so screen readers announce the choice, its position and its checked state. Tab moves into the group and the arrow keys change photo, and the visible thumbnail of the focused radio gets a 3px outline. Hidden photos use visibility: hidden, so only the current one is read, and every photo has descriptive alt text. The crossfade is turned off under prefers-reduced-motion.

Customise it

Change --tv-accent and --tv-radius on .tv and --cs-accent on .cs. For more photos add a radio, a figure and a label, and one line to each nth-of-type list in the CSS. In Tailwind copy the [#id:checked~*_&] classes with the new id.