Open demo

CSS code

HTML
<div><span class="lbl">Filter by category with radio buttons and :has()</span>
<section class="fl" aria-labelledby="fl-title">
  <div class="gh"><div><p class="gh-kicker">Studio Halde</p><h2 id="fl-title">Selected work, 2025</h2></div><p class="gh-meta">12 photographs in 4 categories</p></div>
  <fieldset class="fl-filters"><legend class="fl-sr">Show photographs from</legend>
    <label><input type="radio" name="fl" value="all" checked><span>All <small>12</small></span></label>
    <label><input type="radio" name="fl" value="land"><span>Landscape <small>3</small></span></label>
    <label><input type="radio" name="fl" value="city"><span>City <small>3</small></span></label>
    <label><input type="radio" name="fl" value="food"><span>Food <small>3</small></span></label>
    <label><input type="radio" name="fl" value="wild"><span>Wildlife <small>3</small></span></label>
  </fieldset>
  <ul class="fl-grid" role="list">
    <li class="fl-item" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="Snow-dusted peaks rising above a sea of cloud at sunset"><figcaption><span>Cloud sea</span><span class="fl-tag">Landscape</span></figcaption></figure></li>
    <li class="fl-item" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="Rows of tall towers lit by a low sun, seen from above"><figcaption><span>Golden avenue</span><span class="fl-tag">City</span></figcaption></figure></li>
    <li class="fl-item" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1476224203421-9ac39bcb3327?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="Grilled chicken strips with bread, salad and a dipping sauce on a wooden board, glasses behind"><figcaption><span>Grill board</span><span class="fl-tag">Food</span></figcaption></figure></li>
    <li class="fl-item" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1474511320723-9a56873867b5?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A red fox standing on a snowy bank, looking to one side"><figcaption><span>Red fox</span><span class="fl-tag">Wildlife</span></figcaption></figure></li>
    <li class="fl-item" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A walker on a rock outcrop above a green valley at sunrise"><figcaption><span>The lookout</span><span class="fl-tag">Landscape</span></figcaption></figure></li>
    <li class="fl-item" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1496568816309-51d7c20e3b21?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A city of glowing streets and towers seen from high above at dusk"><figcaption><span>Lights coming on</span><span class="fl-tag">City</span></figcaption></figure></li>
    <li class="fl-item" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1546182990-dffeafbe841d?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A male lion with a thick mane walking across grass beside a tree trunk"><figcaption><span>Lion</span><span class="fl-tag">Wildlife</span></figcaption></figure></li>
    <li class="fl-item" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A sliced pizza topped with chicken, red onion and herbs on a wooden board"><figcaption><span>Flatbread</span><span class="fl-tag">Food</span></figcaption></figure></li>
    <li class="fl-item" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="Green cliffs and a winding road under a cloudy sunrise"><figcaption><span>Cliff road</span><span class="fl-tag">Landscape</span></figcaption></figure></li>
    <li class="fl-item" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A wide city spread out in lights under a deep blue night sky"><figcaption><span>Night grid</span><span class="fl-tag">City</span></figcaption></figure></li>
    <li class="fl-item" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1437622368342-7a3d73a34c8f?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="A sea turtle swimming through clear blue water"><figcaption><span>Sea turtle</span><span class="fl-tag">Wildlife</span></figcaption></figure></li>
    <li class="fl-item" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1488477181946-6428a0291777?auto=format&fit=crop&w=480&h=480&q=70" width="480" height="480" loading="lazy" alt="Small glass jars of a creamy dessert topped with strawberries on a white table"><figcaption><span>Strawberry pots</span><span class="fl-tag">Food</span></figcaption></figure></li>
  </ul>
</section></div>

<div><span class="lbl">Popover lightbox with no script</span>
<section class="pv" aria-labelledby="pv-title">
  <div class="gh"><div><p class="gh-kicker">Print shop</p><h2 id="pv-title">Four open editions</h2></div><p class="gh-meta">Archival pigment prints, signed</p></div>
  <ul class="pv-grid" role="list">
    <li><button class="pv-thumb" type="button" popovertarget="pv1" aria-label="Enlarge Red road"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A road running between red sandstone cliffs"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Red road <span>€240</span></p>
      <div class="pv-pop" id="pv1" popover role="dialog" aria-labelledby="pv1-t"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A road running between red sandstone cliffs"><div class="pv-info"><div><h3 id="pv1-t">Red road</h3><p>Edition of 25 · 50 × 70 cm · €240</p></div><button class="pv-close" type="button" popovertarget="pv1" popovertargetaction="hide" aria-label="Close Red road" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="pv-thumb" type="button" popovertarget="pv2" aria-label="Enlarge Granite wall"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Granite wall <span>€240</span></p>
      <div class="pv-pop" id="pv2" popover role="dialog" aria-labelledby="pv2-t"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><div class="pv-info"><div><h3 id="pv2-t">Granite wall</h3><p>Edition of 25 · 50 × 70 cm · €240</p></div><button class="pv-close" type="button" popovertarget="pv2" popovertargetaction="hide" aria-label="Close Granite wall" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="pv-thumb" type="button" popovertarget="pv3" aria-label="Enlarge The spiral"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">The spiral <span>€180</span></p>
      <div class="pv-pop" id="pv3" popover role="dialog" aria-labelledby="pv3-t"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><div class="pv-info"><div><h3 id="pv3-t">The spiral</h3><p>Edition of 40 · 40 × 50 cm · €180</p></div><button class="pv-close" type="button" popovertarget="pv3" popovertargetaction="hide" aria-label="Close The spiral" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="pv-thumb" type="button" popovertarget="pv4" aria-label="Enlarge Two falls"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Two falls <span>€180</span></p>
      <div class="pv-pop" id="pv4" popover role="dialog" aria-labelledby="pv4-t"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><div class="pv-info"><div><h3 id="pv4-t">Two falls</h3><p>Edition of 40 · 40 × 50 cm · €180</p></div><button class="pv-close" type="button" popovertarget="pv4" popovertargetaction="hide" aria-label="Close Two falls" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
  </ul>
</section></div>
CSS
/* Modern CSS galleries with no JavaScript. Baselines: :has() Chrome 105, Safari 15.4, Firefox 121;
   @starting-style Chrome 117, Safari 17.5, Firefox 129; Popover API Chrome 114, Safari 17, Firefox 125. */
.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:#4f46e5}
.gh h2{margin-top:4px;font-size:1.75rem;line-height:1.15;letter-spacing:-.02em}
.gh-meta{font-size:.9rem;color:#52525b}

/* 1. Filterable gallery: the checked radio hides every other category through :has() */
.fl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.fl-filters{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 22px;padding:0;border:0}
.fl-filters input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.fl-filters span{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border:1.5px solid #a1a1aa;border-radius:999px;font-size:.9rem;font-weight:600;color:#3f3f46;cursor:pointer;transition:background-color .2s,color .2s,border-color .2s}
.fl-filters small{font-size:.75rem;font-weight:600;color:#52525b}
.fl-filters label:hover span{border-color:#18181b}
.fl-filters input:checked + span{background:#18181b;border-color:#18181b;color:#fff}
.fl-filters input:checked + span small{color:#d4d4d8}
.fl-filters input:focus-visible + span{outline:3px solid #4f46e5;outline-offset:2px}
.fl-grid{list-style:none;display:grid;grid-template-columns:repeat(2,1fr);gap:20px 16px}
@media (min-width:768px){.fl-grid{grid-template-columns:repeat(4,1fr)}}
.fl-item{transition:opacity .4s ease-out,scale .4s ease-out}
@starting-style{.fl-item{opacity:0;scale:.94}}
.fl:has([value=land]:checked) .fl-item:not([data-cat=land]),
.fl:has([value=city]:checked) .fl-item:not([data-cat=city]),
.fl:has([value=food]:checked) .fl-item:not([data-cat=food]),
.fl:has([value=wild]:checked) .fl-item:not([data-cat=wild]){display:none}
.fl-item img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;border-radius:12px;background:#e4e4e7}
.fl-item figcaption{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-top:8px;font-size:.92rem;font-weight:600}
.fl-tag{font-size:.75rem;font-weight:500;color:#52525b}

/* 2. Popover lightbox: popovertarget opens it, Esc or a click outside closes it (light dismiss),
   autofocus moves focus to the close button and focus returns to the thumbnail. */
.pv-grid{list-style:none;display:grid;grid-template-columns:repeat(2,1fr);gap:24px 16px}
@media (min-width:768px){.pv-grid{grid-template-columns:repeat(4,1fr)}}
.pv-thumb{position:relative;display:block;width:100%;padding:0;border:0;border-radius:12px;overflow:hidden;background:#e4e4e7;cursor:zoom-in}
.pv-thumb img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;transition:transform .5s ease}
.pv-thumb:hover img{transform:scale(1.04)}
.pv-zoom{position:absolute;right:10px;bottom:10px;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.94);color:#18181b}
.pv-zoom svg{width:18px;height:18px}
.pv-thumb:focus-visible{outline:3px solid #4f46e5;outline-offset:3px}
.pv-name{display:flex;justify-content:space-between;gap:8px;margin-top:10px;font-size:.95rem;font-weight:600}
.pv-name span{font-weight:500;color:#52525b}
.pv-pop{width:min(1000px,calc(100vw - 32px));max-height:calc(100dvh - 32px);margin:auto;padding:0;border:0;border-radius:14px;background:#18181b;color:#fafaf9;overflow:auto;
  opacity:0;scale:.96;transition:opacity .25s ease-out,scale .25s ease-out}
.pv-pop:popover-open{opacity:1;scale:1}
@starting-style{.pv-pop:popover-open{opacity:0;scale:.96}}
.pv-pop::backdrop{background:rgba(9,9,11,.82)}
.pv-pop img{display:block;width:100%;max-height:calc(100dvh - 140px);object-fit:contain;background:#09090b}
.pv-info{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px}
.pv-info h3{font-size:1.15rem;font-weight:600}
.pv-info p{margin-top:2px;font-size:.88rem;color:#d4d4d8}
.pv-close{flex:none;display:grid;place-items:center;width:42px;height:42px;padding:0;border:0;border-radius:50%;background:rgba(255,255,255,.12);color:#fff;cursor:pointer;transition:background-color .2s}
.pv-close:hover{background:rgba(255,255,255,.24)}
.pv-close svg{width:18px;height:18px}
.pv-close:focus-visible{outline:3px solid #a5b4fc;outline-offset:3px}
@media (prefers-reduced-motion:reduce){.fl-filters span,.fl-item,.pv-thumb img,.pv-pop,.pv-close{transition:none}.pv-thumb:hover img{transform:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Filter by category with radio buttons and :has()</span>
<section class="fl" aria-labelledby="fl-title">
  <div class="gh d-flex flex-wrap align-items-end justify-content-between"><div><p class="gh-kicker">Studio Halde</p><h2 id="fl-title" class="gh-title">Selected work, 2025</h2></div><p class="gh-meta">12 photographs in 4 categories</p></div>
  <fieldset class="fl-filters d-flex flex-wrap"><legend class="visually-hidden">Show photographs from</legend>
    <input type="radio" class="btn-check" name="fl" id="fl-all" value="all" checked><label class="btn rounded-pill" for="fl-all">All <small>12</small></label>
    <input type="radio" class="btn-check" name="fl" id="fl-land" value="land"><label class="btn rounded-pill" for="fl-land">Landscape <small>3</small></label>
    <input type="radio" class="btn-check" name="fl" id="fl-city" value="city"><label class="btn rounded-pill" for="fl-city">City <small>3</small></label>
    <input type="radio" class="btn-check" name="fl" id="fl-food" value="food"><label class="btn rounded-pill" for="fl-food">Food <small>3</small></label>
    <input type="radio" class="btn-check" name="fl" id="fl-wild" value="wild"><label class="btn rounded-pill" for="fl-wild">Wildlife <small>3</small></label>
  </fieldset>
  <div class="row row-cols-2 row-cols-md-4 fl-grid">
    <div class="col fl-item" data-cat="land"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="Snow-dusted peaks rising above a sea of cloud at sunset"><figcaption class="figure-caption"><span>Cloud sea</span><span class="fl-tag">Landscape</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="city"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="Rows of tall towers lit by a low sun, seen from above"><figcaption class="figure-caption"><span>Golden avenue</span><span class="fl-tag">City</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="food"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1476224203421-9ac39bcb3327?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="Grilled chicken strips with bread, salad and a dipping sauce on a wooden board, glasses behind"><figcaption class="figure-caption"><span>Grill board</span><span class="fl-tag">Food</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="wild"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1474511320723-9a56873867b5?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A red fox standing on a snowy bank, looking to one side"><figcaption class="figure-caption"><span>Red fox</span><span class="fl-tag">Wildlife</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="land"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A walker on a rock outcrop above a green valley at sunrise"><figcaption class="figure-caption"><span>The lookout</span><span class="fl-tag">Landscape</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="city"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1496568816309-51d7c20e3b21?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A city of glowing streets and towers seen from high above at dusk"><figcaption class="figure-caption"><span>Lights coming on</span><span class="fl-tag">City</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="wild"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1546182990-dffeafbe841d?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A male lion with a thick mane walking across grass beside a tree trunk"><figcaption class="figure-caption"><span>Lion</span><span class="fl-tag">Wildlife</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="food"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A sliced pizza topped with chicken, red onion and herbs on a wooden board"><figcaption class="figure-caption"><span>Flatbread</span><span class="fl-tag">Food</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="land"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="Green cliffs and a winding road under a cloudy sunrise"><figcaption class="figure-caption"><span>Cliff road</span><span class="fl-tag">Landscape</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="city"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A wide city spread out in lights under a deep blue night sky"><figcaption class="figure-caption"><span>Night grid</span><span class="fl-tag">City</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="wild"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1437622368342-7a3d73a34c8f?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="A sea turtle swimming through clear blue water"><figcaption class="figure-caption"><span>Sea turtle</span><span class="fl-tag">Wildlife</span></figcaption></figure></div>
    <div class="col fl-item" data-cat="food"><figure class="figure m-0 d-block"><img src="https://images.unsplash.com/photo-1488477181946-6428a0291777?auto=format&fit=crop&w=480&h=480&q=70" class="figure-img img-fluid" width="480" height="480" loading="lazy" alt="Small glass jars of a creamy dessert topped with strawberries on a white table"><figcaption class="figure-caption"><span>Strawberry pots</span><span class="fl-tag">Food</span></figcaption></figure></div>
  </div>
</section></div>

<div><span class="lbl">Popover lightbox with no script</span>
<section class="pv" aria-labelledby="pv-title">
  <div class="gh d-flex flex-wrap align-items-end justify-content-between"><div><p class="gh-kicker">Print shop</p><h2 id="pv-title" class="gh-title">Four open editions</h2></div><p class="gh-meta">Archival pigment prints, signed</p></div>
  <div class="row row-cols-2 row-cols-md-4 pv-grid">
    <div class="col"><button class="pv-thumb" type="button" popovertarget="pv1" aria-label="Enlarge Red road"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A road running between red sandstone cliffs"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Red road <span>€240</span></p>
      <div class="pv-pop" id="pv1" popover role="dialog" aria-labelledby="pv1-t" data-bs-theme="dark"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A road running between red sandstone cliffs"><div class="pv-info"><div><h3 id="pv1-t">Red road</h3><p>Edition of 25 · 50 × 70 cm · €240</p></div><button class="pv-close" type="button" popovertarget="pv1" popovertargetaction="hide" aria-label="Close Red road" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></div>
    <div class="col"><button class="pv-thumb" type="button" popovertarget="pv2" aria-label="Enlarge Granite wall"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Granite wall <span>€240</span></p>
      <div class="pv-pop" id="pv2" popover role="dialog" aria-labelledby="pv2-t" data-bs-theme="dark"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><div class="pv-info"><div><h3 id="pv2-t">Granite wall</h3><p>Edition of 25 · 50 × 70 cm · €240</p></div><button class="pv-close" type="button" popovertarget="pv2" popovertargetaction="hide" aria-label="Close Granite wall" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></div>
    <div class="col"><button class="pv-thumb" type="button" popovertarget="pv3" aria-label="Enlarge The spiral"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">The spiral <span>€180</span></p>
      <div class="pv-pop" id="pv3" popover role="dialog" aria-labelledby="pv3-t" data-bs-theme="dark"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><div class="pv-info"><div><h3 id="pv3-t">The spiral</h3><p>Edition of 40 · 40 × 50 cm · €180</p></div><button class="pv-close" type="button" popovertarget="pv3" popovertargetaction="hide" aria-label="Close The spiral" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></div>
    <div class="col"><button class="pv-thumb" type="button" popovertarget="pv4" aria-label="Enlarge Two falls"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=600&h=750&q=70" width="600" height="750" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><span class="pv-zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="pv-name">Two falls <span>€180</span></p>
      <div class="pv-pop" id="pv4" popover role="dialog" aria-labelledby="pv4-t" data-bs-theme="dark"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=1600&q=70" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><div class="pv-info"><div><h3 id="pv4-t">Two falls</h3><p>Edition of 40 · 40 × 50 cm · €180</p></div><button class="pv-close" type="button" popovertarget="pv4" popovertargetaction="hide" aria-label="Close Two falls" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></div>
  </div>
</section></div>
CSS
/* Modern CSS galleries on Bootstrap, no JavaScript: .btn-check radios restyled as pills filter a
   .row-cols-* grid through :has(), and the native Popover API opens the prints.
   Baselines: :has() Chrome 105, Safari 15.4, Firefox 121; @starting-style Chrome 117, Safari 17.5,
   Firefox 129; Popover API Chrome 114, Safari 17, Firefox 125. */
.gh{gap:6px 24px;margin-bottom:20px}
.gh-kicker{margin:0;font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#4f46e5}
.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:#52525b}

/* 1. Filterable gallery */
.fl-filters{gap:8px;margin:0 0 22px;padding:0;border:0}
.fl-filters .btn{--bs-btn-padding-x:16px;--bs-btn-padding-y:8px;--bs-btn-font-size:.9rem;--bs-btn-font-weight:600;--bs-btn-line-height:normal;--bs-btn-color:#3f3f46;--bs-btn-border-color:#a1a1aa;--bs-btn-border-width:1.5px;
  --bs-btn-hover-color:#3f3f46;--bs-btn-hover-bg:transparent;--bs-btn-hover-border-color:#18181b;--bs-btn-active-color:#fff;--bs-btn-active-bg:#18181b;--bs-btn-active-border-color:#18181b;
  display:inline-flex;align-items:center;gap:8px}
.fl-filters small{font-size:.75rem;font-weight:600;color:#52525b}
.fl-filters .btn-check:checked + .btn small{color:#d4d4d8}
.fl-filters .btn-check:focus-visible + .btn{box-shadow:none;outline:3px solid #4f46e5;outline-offset:2px}
.fl-grid{--bs-gutter-x:16px;--bs-gutter-y:20px}
.fl-item{transition:opacity .4s ease-out,scale .4s ease-out}
@starting-style{.fl-item{opacity:0;scale:.94}}
.fl:has([value=land]:checked) .fl-item:not([data-cat=land]),
.fl:has([value=city]:checked) .fl-item:not([data-cat=city]),
.fl:has([value=food]:checked) .fl-item:not([data-cat=food]),
.fl:has([value=wild]:checked) .fl-item:not([data-cat=wild]){display:none}
.fl .figure-img{width:100%;aspect-ratio:1;object-fit:cover;margin:0;border-radius:12px;background:#e4e4e7}
.fl .figure-caption{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-top:8px;font-size:.92rem;font-weight:600;color:#18181b}
.fl-tag{font-size:.75rem;font-weight:500;color:#52525b}

.pv-grid{--bs-gutter-x:16px;--bs-gutter-y:24px}

/* 2. Popover lightbox: popovertarget opens it, Esc or a click outside closes it (light dismiss),
   autofocus moves focus to the close button and focus returns to the thumbnail. */
.pv-thumb{position:relative;display:block;width:100%;padding:0;border:0;border-radius:12px;overflow:hidden;background:#e4e4e7;cursor:zoom-in}
.pv-thumb img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;transition:transform .5s ease}
.pv-thumb:hover img{transform:scale(1.04)}
.pv-zoom{position:absolute;right:10px;bottom:10px;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.94);color:#18181b}
.pv-zoom svg{width:18px;height:18px}
.pv-thumb:focus-visible{outline:3px solid #4f46e5;outline-offset:3px}
.pv-name{display:flex;justify-content:space-between;gap:8px;margin:10px 0 0;font-size:.95rem;font-weight:600}
.pv-name span{font-weight:500;color:#52525b}
.pv-pop{width:min(1000px,calc(100vw - 32px));max-height:calc(100dvh - 32px);margin:auto;padding:0;border:0;border-radius:14px;background:#18181b;color:#fafaf9;overflow:auto;
  opacity:0;scale:.96;transition:opacity .25s ease-out,scale .25s ease-out}
.pv-pop:popover-open{opacity:1;scale:1}
@starting-style{.pv-pop:popover-open{opacity:0;scale:.96}}
.pv-pop::backdrop{background:rgba(9,9,11,.82)}
.pv-pop img{display:block;width:100%;max-height:calc(100dvh - 140px);object-fit:contain;background:#09090b}
.pv-info{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px}
.pv-info h3{margin:0;font-size:1.15rem;font-weight:600}
.pv-info p{margin:2px 0 0;font-size:.88rem;color:#d4d4d8}
.pv-close{flex:none;display:grid;place-items:center;width:42px;height:42px;padding:0;border:0;border-radius:50%;background:rgba(255,255,255,.12);color:#fff;cursor:pointer;transition:background-color .2s}
.pv-close:hover{background:rgba(255,255,255,.24)}
.pv-close svg{width:18px;height:18px}
.pv-close:focus-visible{outline:3px solid #a5b4fc;outline-offset:3px}
@media (prefers-reduced-motion:reduce){.fl-filters .btn,.fl-item,.pv-thumb img,.pv-pop,.pv-close{transition:none}.pv-thumb:hover img{transform:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#52525b] uppercase">Filter by category with radio buttons and :has()</span>
<section class="group/fl" aria-labelledby="fl-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-[#4f46e5] uppercase">Studio Halde</p><h2 id="fl-title" class="mt-1 text-[1.75rem] leading-[1.15] font-bold tracking-[-.02em]">Selected work, 2025</h2></div><p class="text-[.9rem] leading-[normal] text-[#52525b]">12 photographs in 4 categories</p></div>
  <fieldset class="mb-[22px] flex flex-wrap gap-2"><legend class="sr-only">Show photographs from</legend>
    <label><input class="peer sr-only" type="radio" name="fl" value="all" checked><span class="group/p inline-flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-[#a1a1aa] px-4 py-2 text-[.9rem] font-semibold text-[#3f3f46] transition-colors duration-200 hover:border-[#18181b] peer-checked:border-[#18181b] peer-checked:bg-[#18181b] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">All <small class="text-xs font-semibold text-[#52525b] group-[:is(:checked+*)]/p:text-[#d4d4d8]">12</small></span></label>
    <label><input class="peer sr-only" type="radio" name="fl" value="land"><span class="group/p inline-flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-[#a1a1aa] px-4 py-2 text-[.9rem] font-semibold text-[#3f3f46] transition-colors duration-200 hover:border-[#18181b] peer-checked:border-[#18181b] peer-checked:bg-[#18181b] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Landscape <small class="text-xs font-semibold text-[#52525b] group-[:is(:checked+*)]/p:text-[#d4d4d8]">3</small></span></label>
    <label><input class="peer sr-only" type="radio" name="fl" value="city"><span class="group/p inline-flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-[#a1a1aa] px-4 py-2 text-[.9rem] font-semibold text-[#3f3f46] transition-colors duration-200 hover:border-[#18181b] peer-checked:border-[#18181b] peer-checked:bg-[#18181b] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">City <small class="text-xs font-semibold text-[#52525b] group-[:is(:checked+*)]/p:text-[#d4d4d8]">3</small></span></label>
    <label><input class="peer sr-only" type="radio" name="fl" value="food"><span class="group/p inline-flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-[#a1a1aa] px-4 py-2 text-[.9rem] font-semibold text-[#3f3f46] transition-colors duration-200 hover:border-[#18181b] peer-checked:border-[#18181b] peer-checked:bg-[#18181b] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Food <small class="text-xs font-semibold text-[#52525b] group-[:is(:checked+*)]/p:text-[#d4d4d8]">3</small></span></label>
    <label><input class="peer sr-only" type="radio" name="fl" value="wild"><span class="group/p inline-flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-[#a1a1aa] px-4 py-2 text-[.9rem] font-semibold text-[#3f3f46] transition-colors duration-200 hover:border-[#18181b] peer-checked:border-[#18181b] peer-checked:bg-[#18181b] peer-checked:text-white peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none">Wildlife <small class="text-xs font-semibold text-[#52525b] group-[:is(:checked+*)]/p:text-[#d4d4d8]">3</small></span></label>
  </fieldset>
  <ul class="grid grid-cols-2 gap-x-4 gap-y-5 md:grid-cols-4" role="list">
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="Snow-dusted peaks rising above a sea of cloud at sunset"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Cloud sea</span><span class="text-xs font-medium text-[#52525b]">Landscape</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="Rows of tall towers lit by a low sun, seen from above"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Golden avenue</span><span class="text-xs font-medium text-[#52525b]">City</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1476224203421-9ac39bcb3327?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="Grilled chicken strips with bread, salad and a dipping sauce on a wooden board, glasses behind"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Grill board</span><span class="text-xs font-medium text-[#52525b]">Food</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1474511320723-9a56873867b5?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A red fox standing on a snowy bank, looking to one side"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Red fox</span><span class="text-xs font-medium text-[#52525b]">Wildlife</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A walker on a rock outcrop above a green valley at sunrise"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>The lookout</span><span class="text-xs font-medium text-[#52525b]">Landscape</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1496568816309-51d7c20e3b21?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A city of glowing streets and towers seen from high above at dusk"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Lights coming on</span><span class="text-xs font-medium text-[#52525b]">City</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1546182990-dffeafbe841d?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A male lion with a thick mane walking across grass beside a tree trunk"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Lion</span><span class="text-xs font-medium text-[#52525b]">Wildlife</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A sliced pizza topped with chicken, red onion and herbs on a wooden board"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Flatbread</span><span class="text-xs font-medium text-[#52525b]">Food</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="land"><figure><img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="Green cliffs and a winding road under a cloudy sunrise"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Cliff road</span><span class="text-xs font-medium text-[#52525b]">Landscape</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="city"><figure><img src="https://images.unsplash.com/photo-1444723121867-7a241cacace9?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A wide city spread out in lights under a deep blue night sky"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Night grid</span><span class="text-xs font-medium text-[#52525b]">City</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=food]:checked]/fl:hidden" data-cat="wild"><figure><img src="https://images.unsplash.com/photo-1437622368342-7a3d73a34c8f?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="A sea turtle swimming through clear blue water"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Sea turtle</span><span class="text-xs font-medium text-[#52525b]">Wildlife</span></figcaption></figure></li>
    <li class="transition-[opacity,scale] duration-400 ease-out starting:scale-94 starting:opacity-0 motion-reduce:transition-none group-has-[[value=land]:checked]/fl:hidden group-has-[[value=city]:checked]/fl:hidden group-has-[[value=wild]:checked]/fl:hidden" data-cat="food"><figure><img src="https://images.unsplash.com/photo-1488477181946-6428a0291777?auto=format&fit=crop&w=480&h=480&q=70" class="block aspect-square h-auto w-full rounded-xl bg-[#e4e4e7] object-cover" width="480" height="480" loading="lazy" alt="Small glass jars of a creamy dessert topped with strawberries on a white table"><figcaption class="mt-2 flex items-baseline justify-between gap-2 text-[.92rem] font-semibold"><span>Strawberry pots</span><span class="text-xs font-medium text-[#52525b]">Food</span></figcaption></figure></li>
  </ul>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#52525b] uppercase">Popover lightbox with no script</span>
<section aria-labelledby="pv-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-[#4f46e5] uppercase">Print shop</p><h2 id="pv-title" class="mt-1 text-[1.75rem] leading-[1.15] font-bold tracking-[-.02em]">Four open editions</h2></div><p class="text-[.9rem] leading-[normal] text-[#52525b]">Archival pigment prints, signed</p></div>
  <ul class="grid grid-cols-2 gap-x-4 gap-y-6 md:grid-cols-4" role="list">
    <li><button class="group relative block w-full cursor-zoom-in overflow-hidden rounded-xl bg-[#e4e4e7] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5]" type="button" popovertarget="pv1" aria-label="Enlarge Red road"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=600&h=750&q=70" class="block aspect-[4/5] h-auto w-full object-cover transition-transform duration-500 group-hover:scale-104 motion-reduce:transition-none motion-reduce:group-hover:scale-100" width="600" height="750" loading="lazy" alt="A road running between red sandstone cliffs"><span class="absolute right-2.5 bottom-2.5 grid size-9 place-items-center rounded-full bg-white/94 text-[#18181b] [&>svg]:size-[18px]"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="mt-2.5 flex justify-between gap-2 text-[.95rem] font-semibold">Red road <span class="font-medium text-[#52525b]">€240</span></p>
      <div class="m-auto max-h-[calc(100dvh-32px)] w-[min(1000px,calc(100vw-32px))] scale-96 overflow-auto rounded-[14px] border-0 bg-[#18181b] p-0 text-[#fafaf9] opacity-0 transition-[opacity,scale] duration-250 ease-out backdrop:bg-[rgba(9,9,11,.82)] open:scale-100 open:opacity-100 starting:open:scale-96 starting:open:opacity-0 motion-reduce:transition-none" id="pv1" popover role="dialog" aria-labelledby="pv1-t"><img src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1600&q=70" class="block max-h-[calc(100dvh-140px)] w-full bg-[#09090b] object-contain" loading="lazy" alt="A road running between red sandstone cliffs"><div class="flex items-center justify-between gap-4 px-5 py-4"><div><h3 id="pv1-t" class="text-[1.15rem] font-semibold">Red road</h3><p class="mt-0.5 text-[.88rem] text-[#d4d4d8]">Edition of 25 · 50 × 70 cm · €240</p></div><button class="grid size-[42px] flex-none cursor-pointer place-items-center rounded-full bg-white/12 text-white transition-colors duration-200 hover:bg-white/24 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] motion-reduce:transition-none [&>svg]:size-[18px]" type="button" popovertarget="pv1" popovertargetaction="hide" aria-label="Close Red road" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="group relative block w-full cursor-zoom-in overflow-hidden rounded-xl bg-[#e4e4e7] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5]" type="button" popovertarget="pv2" aria-label="Enlarge Granite wall"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=600&h=750&q=70" class="block aspect-[4/5] h-auto w-full object-cover transition-transform duration-500 group-hover:scale-104 motion-reduce:transition-none motion-reduce:group-hover:scale-100" width="600" height="750" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><span class="absolute right-2.5 bottom-2.5 grid size-9 place-items-center rounded-full bg-white/94 text-[#18181b] [&>svg]:size-[18px]"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="mt-2.5 flex justify-between gap-2 text-[.95rem] font-semibold">Granite wall <span class="font-medium text-[#52525b]">€240</span></p>
      <div class="m-auto max-h-[calc(100dvh-32px)] w-[min(1000px,calc(100vw-32px))] scale-96 overflow-auto rounded-[14px] border-0 bg-[#18181b] p-0 text-[#fafaf9] opacity-0 transition-[opacity,scale] duration-250 ease-out backdrop:bg-[rgba(9,9,11,.82)] open:scale-100 open:opacity-100 starting:open:scale-96 starting:open:opacity-0 motion-reduce:transition-none" id="pv2" popover role="dialog" aria-labelledby="pv2-t"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=1600&q=70" class="block max-h-[calc(100dvh-140px)] w-full bg-[#09090b] object-contain" loading="lazy" alt="A sunlit meadow with tall pines below a sheer granite cliff"><div class="flex items-center justify-between gap-4 px-5 py-4"><div><h3 id="pv2-t" class="text-[1.15rem] font-semibold">Granite wall</h3><p class="mt-0.5 text-[.88rem] text-[#d4d4d8]">Edition of 25 · 50 × 70 cm · €240</p></div><button class="grid size-[42px] flex-none cursor-pointer place-items-center rounded-full bg-white/12 text-white transition-colors duration-200 hover:bg-white/24 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] motion-reduce:transition-none [&>svg]:size-[18px]" type="button" popovertarget="pv2" popovertargetaction="hide" aria-label="Close Granite wall" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="group relative block w-full cursor-zoom-in overflow-hidden rounded-xl bg-[#e4e4e7] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5]" type="button" popovertarget="pv3" aria-label="Enlarge The spiral"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=600&h=750&q=70" class="block aspect-[4/5] h-auto w-full object-cover transition-transform duration-500 group-hover:scale-104 motion-reduce:transition-none motion-reduce:group-hover:scale-100" width="600" height="750" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><span class="absolute right-2.5 bottom-2.5 grid size-9 place-items-center rounded-full bg-white/94 text-[#18181b] [&>svg]:size-[18px]"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="mt-2.5 flex justify-between gap-2 text-[.95rem] font-semibold">The spiral <span class="font-medium text-[#52525b]">€180</span></p>
      <div class="m-auto max-h-[calc(100dvh-32px)] w-[min(1000px,calc(100vw-32px))] scale-96 overflow-auto rounded-[14px] border-0 bg-[#18181b] p-0 text-[#fafaf9] opacity-0 transition-[opacity,scale] duration-250 ease-out backdrop:bg-[rgba(9,9,11,.82)] open:scale-100 open:opacity-100 starting:open:scale-96 starting:open:opacity-0 motion-reduce:transition-none" id="pv3" popover role="dialog" aria-labelledby="pv3-t"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=1600&q=70" class="block max-h-[calc(100dvh-140px)] w-full bg-[#09090b] object-contain" loading="lazy" alt="A spiral of stones on a green hillside under a sunset sky"><div class="flex items-center justify-between gap-4 px-5 py-4"><div><h3 id="pv3-t" class="text-[1.15rem] font-semibold">The spiral</h3><p class="mt-0.5 text-[.88rem] text-[#d4d4d8]">Edition of 40 · 40 × 50 cm · €180</p></div><button class="grid size-[42px] flex-none cursor-pointer place-items-center rounded-full bg-white/12 text-white transition-colors duration-200 hover:bg-white/24 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] motion-reduce:transition-none [&>svg]:size-[18px]" type="button" popovertarget="pv3" popovertargetaction="hide" aria-label="Close The spiral" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
    <li><button class="group relative block w-full cursor-zoom-in overflow-hidden rounded-xl bg-[#e4e4e7] focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#4f46e5]" type="button" popovertarget="pv4" aria-label="Enlarge Two falls"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=600&h=750&q=70" class="block aspect-[4/5] h-auto w-full object-cover transition-transform duration-500 group-hover:scale-104 motion-reduce:transition-none motion-reduce:group-hover:scale-100" width="600" height="750" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><span class="absolute right-2.5 bottom-2.5 grid size-9 place-items-center rounded-full bg-white/94 text-[#18181b] [&>svg]:size-[18px]"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4M11 8v6M8 11h6"/></svg></span></button><p class="mt-2.5 flex justify-between gap-2 text-[.95rem] font-semibold">Two falls <span class="font-medium text-[#52525b]">€180</span></p>
      <div class="m-auto max-h-[calc(100dvh-32px)] w-[min(1000px,calc(100vw-32px))] scale-96 overflow-auto rounded-[14px] border-0 bg-[#18181b] p-0 text-[#fafaf9] opacity-0 transition-[opacity,scale] duration-250 ease-out backdrop:bg-[rgba(9,9,11,.82)] open:scale-100 open:opacity-100 starting:open:scale-96 starting:open:opacity-0 motion-reduce:transition-none" id="pv4" popover role="dialog" aria-labelledby="pv4-t"><img src="https://images.unsplash.com/photo-1433086966358-54859d0ed716?auto=format&fit=crop&w=1600&q=70" class="block max-h-[calc(100dvh-140px)] w-full bg-[#09090b] object-contain" loading="lazy" alt="A tall two-tier waterfall in a green gorge, with a stone footbridge across the middle"><div class="flex items-center justify-between gap-4 px-5 py-4"><div><h3 id="pv4-t" class="text-[1.15rem] font-semibold">Two falls</h3><p class="mt-0.5 text-[.88rem] text-[#d4d4d8]">Edition of 40 · 40 × 50 cm · €180</p></div><button class="grid size-[42px] flex-none cursor-pointer place-items-center rounded-full bg-white/12 text-white transition-colors duration-200 hover:bg-white/24 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] motion-reduce:transition-none [&>svg]:size-[18px]" type="button" popovertarget="pv4" popovertargetaction="hide" aria-label="Close Two falls" autofocus><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div></div></li>
  </ul>
</section></div>

About this set

Two gallery features that usually need JavaScript, done in CSS. Filter by category with radio buttons and :has() shows twelve photographs from four categories with a row of pill filters (All, Landscape, City, Food and Wildlife, each with a count); the filters are real radio buttons, and rules such as .fl:has([value=city]:checked) .fl-item:not([data-cat=city]) hide every other category. Photos that come back fade and grow in through @starting-style. Popover lightbox with no script is a print shop of four editions: each thumbnail is a button with popovertarget, which opens a dark popover with the large photo, the title, edition, size and price, and a close button. The browser handles light dismiss, so Esc or a click outside closes it, autofocus puts focus on the close button and focus goes back to the thumbnail. Use the filter for portfolios and the popover where a full modal dialog is more than you need. The Bootstrap version styles the filters as .btn-check pills, and the Tailwind version uses has-, peer-checked:, starting: and open: variants.

What’s included

  • Category filter built from radio buttons and :has(), with no JavaScript
  • Filtered photos fade back in with @starting-style
  • Lightbox built on the native Popover API with light dismiss
  • Close button gets focus on open and focus returns to the thumbnail
  • Bootstrap version uses .btn-check pills and .row-cols-* grids

Accessibility

The filters are a fieldset of native radio buttons with a hidden legend, so they are announced as a group and change with the arrow keys; hidden photos use display: none and leave the reading order. The print buttons are named Enlarge and the title, each popover has role="dialog" and is named by its heading, and the close button is labelled. Every control shows a visible focus outline, and the fades and zoom are switched off under prefers-reduced-motion.

Customise it

Change the pill colours in .fl-filters span (Bootstrap: the --bs-btn-* variables on .fl-filters .btn), the categories in the data-cat values and :has() rules, and the popover colours in .pv-pop and .pv-pop::backdrop. In Tailwind edit the group-has-[[value=…]:checked]/fl:hidden classes and the open: and backdrop: utilities.