Open demo

CSS code

HTML
<div><span class="lbl">Radio-button carousel (no JavaScript)</span>
<section class="tour" role="region" aria-roledescription="carousel" aria-label="Pennywise tour">
  <div class="tour-view">
    <div class="tour-track">
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="1 of 3">
        <div class="tour-art art-1"></div><h3>Track every expense</h3><p>Snap a receipt and Pennywise files it under the right category before you have put your wallet away.</p>
      </div>
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="2 of 3">
        <div class="tour-art art-2"></div><h3>Split bills in two taps</h3><p>Pick the people and the amount. Everyone gets a link and pays you back from their own banking app.</p>
      </div>
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="3 of 3">
        <div class="tour-art art-3"></div><h3>See where it goes</h3><p>A monthly ring shows groceries, rent and everything else at a glance, with no spreadsheets.</p>
      </div>
    </div>
  </div>
  <div class="tour-dots" role="radiogroup" aria-label="Choose a slide">
    <label><input type="radio" name="tour" aria-label="Slide 1: Track every expense" checked><span></span></label>
    <label><input type="radio" name="tour" aria-label="Slide 2: Split bills in two taps"><span></span></label>
    <label><input type="radio" name="tour" aria-label="Slide 3: See where it goes"><span></span></label>
  </div>
</section></div>

<div><span class="lbl">Native scroll buttons and markers</span>
<section class="posters" role="region" aria-roledescription="carousel" aria-label="City posters">
  <div class="posters-track" tabindex="0">
    <div class="poster" role="group" aria-roledescription="slide" aria-label="1 of 5">
      <div class="poster-art poster-lisbon"></div>
      <div class="poster-body"><div><h3>Lisbon</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster" role="group" aria-roledescription="slide" aria-label="2 of 5">
      <div class="poster-art poster-kyoto"></div>
      <div class="poster-body"><div><h3>Kyoto</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster" role="group" aria-roledescription="slide" aria-label="3 of 5">
      <div class="poster-art poster-reykjavik"></div>
      <div class="poster-body"><div><h3>Reykjavík</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster" role="group" aria-roledescription="slide" aria-label="4 of 5">
      <div class="poster-art poster-oaxaca"></div>
      <div class="poster-body"><div><h3>Oaxaca</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster" role="group" aria-roledescription="slide" aria-label="5 of 5">
      <div class="poster-art poster-marrakech"></div>
      <div class="poster-body"><div><h3>Marrakech</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
  </div>
  <p class="posters-hint">Scroll or swipe sideways for all five posters.</p>
</section></div>

<div><span class="lbl">Scroll-driven coverflow</span>
<section class="shelf3d" role="region" aria-roledescription="carousel" aria-label="Staff picks">
  <h2>Staff picks for October</h2><p class="shelf3d-sub">Seven novels our booksellers could not put down</p>
  <div class="shelf3d-track" tabindex="0">
    <div class="book book-1" role="group" aria-roledescription="slide" aria-label="1 of 7"><h3>The Salt Road</h3><p>Mara Ellison</p></div>
    <div class="book book-2" role="group" aria-roledescription="slide" aria-label="2 of 7"><h3>Small Hours</h3><p>Idris Coleman</p></div>
    <div class="book book-3" role="group" aria-roledescription="slide" aria-label="3 of 7"><h3>A Map of Rain</h3><p>June Okafor</p></div>
    <div class="book book-4" role="group" aria-roledescription="slide" aria-label="4 of 7"><h3>Glasshouse</h3><p>Theo Varga</p></div>
    <div class="book book-5" role="group" aria-roledescription="slide" aria-label="5 of 7"><h3>The Quiet Year</h3><p>Ines Duarte</p></div>
    <div class="book book-6" role="group" aria-roledescription="slide" aria-label="6 of 7"><h3>Paper Boats</h3><p>Kenji Mori</p></div>
    <div class="book book-7" role="group" aria-roledescription="slide" aria-label="7 of 7"><h3>Low Tide</h3><p>Ada Brennan</p></div>
  </div>
</section></div>
CSS
/* 1. Radio carousel: the dots are radio buttons, so arrow keys move between slides and no script is needed.
   :has() reads which radio is checked and slides the track. Works in every current browser. */
.tour{--tour-accent:#0f766e;max-width:440px;margin:0 auto;padding:22px;border-radius:24px;background:#fff;box-shadow:0 18px 40px rgba(17,24,39,.08)}
.tour-view{overflow:hidden;border-radius:18px}
.tour-track{display:flex;transform:translateX(calc(var(--i,0) * -100%));transition:transform .45s cubic-bezier(.2,.7,.2,1)}
.tour:has(.tour-dots label:nth-child(2) :checked){--i:1}
.tour:has(.tour-dots label:nth-child(3) :checked){--i:2}
.tour-slide{flex:0 0 100%;visibility:hidden;transition:visibility 0s .45s}
.tour:has(.tour-dots label:nth-child(1) :checked) .tour-slide:nth-child(1),
.tour:has(.tour-dots label:nth-child(2) :checked) .tour-slide:nth-child(2),
.tour:has(.tour-dots label:nth-child(3) :checked) .tour-slide:nth-child(3){visibility:visible;transition-delay:0s}
.tour-art{height:220px;border-radius:18px}
.art-1{background:repeating-linear-gradient(to bottom,#fff 0 10px,transparent 10px 22px) 50% 55%/46% 52% no-repeat,linear-gradient(#0f766e,#0f766e) 50% 55%/56% 64% no-repeat,radial-gradient(circle at 80% 20%,#99f6e4 0 12%,transparent 12.5%),linear-gradient(135deg,#ccfbf1,#5eead4)}
.art-2{background:radial-gradient(circle at 40% 50%,rgba(15,118,110,.9) 0 22%,transparent 22.5%),radial-gradient(circle at 60% 50%,rgba(251,191,36,.9) 0 22%,transparent 22.5%),linear-gradient(135deg,#fef3c7,#fde68a)}
.art-3{background:radial-gradient(circle at 50% 50%,#e0f2fe 0 16%,transparent 16.5%),radial-gradient(circle at 50% 50%,transparent 0 32%,#e0f2fe 32.5%),conic-gradient(from 20deg at 50% 50%,#0f766e 0 38%,#f59e0b 0 62%,#6366f1 0 80%,#e11d48 0 100%)}
.tour-slide h3{margin-top:22px;font-size:1.35rem;letter-spacing:-.02em;color:#111827}
.tour-slide p{margin-top:8px;line-height:1.6;color:#4b5563}
.tour-dots{display:flex;justify-content:center;gap:2px;margin-top:20px}
.tour-dots label{position:relative;width:24px;height:24px;display:grid;place-items:center;cursor:pointer;transition:width .3s}
.tour-dots input{position:absolute;inset:0;margin:0;opacity:0;cursor:pointer}
.tour-dots span{width:10px;height:10px;border-radius:5px;background:#6b7280;transition:width .3s,background .3s}
.tour-dots label:has(:checked){width:38px}
.tour-dots label:has(:checked) span{width:28px;background:var(--tour-accent)}
.tour-dots input:focus-visible + span{outline:3px solid var(--tour-accent);outline-offset:3px}

/* 2. Native scroll buttons and markers (Chrome and Edge 135+): the browser generates the arrows and
   the dots from CSS. Elsewhere it is a plain swipeable scroll-snap row with a scrollbar. */
.posters{--post-gap:18px;--post-per:3;position:relative}
.posters-track{display:flex;gap:var(--post-gap);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;padding-bottom:6px;anchor-name:--posters}
.poster{flex:0 0 calc((100% - (var(--post-per) - 1) * var(--post-gap)) / var(--post-per));scroll-snap-align:start;border-radius:16px;overflow:hidden;background:#fff;
  box-shadow:0 1px 2px rgba(17,24,39,.06),0 10px 24px rgba(17,24,39,.06)}
.poster-art{aspect-ratio:4/5}
.poster-lisbon{background:radial-gradient(circle at 30% 32%,#ea580c 0 15%,transparent 15.5%),radial-gradient(120% 60% at 80% 100%,#9a3412 0 50%,transparent 50.5%),linear-gradient(to top,#0e7490 0 22%,transparent 22%),linear-gradient(#fde68a,#fdba74)}
.poster-kyoto{background:radial-gradient(circle at 68% 34%,#e11d48 0 14%,transparent 14.5%),radial-gradient(90% 50% at 15% 100%,#4c0519 0 55%,transparent 55.5%),radial-gradient(90% 45% at 90% 100%,#881337 0 55%,transparent 55.5%),linear-gradient(#ffe4e6,#fbcfe8)}
.poster-reykjavik{background:radial-gradient(70% 16% at 40% 30%,rgba(52,211,153,.85),transparent 70%),radial-gradient(60% 12% at 65% 40%,rgba(167,139,250,.7),transparent 70%),radial-gradient(120% 55% at 50% 108%,#e2e8f0 0 45%,transparent 45.5%),linear-gradient(#0f172a,#1e3a8a)}
.poster-oaxaca{background:radial-gradient(circle at 50% 38%,#dc2626 0 18%,transparent 18.5%),radial-gradient(110% 50% at 0% 100%,#7c2d12 0 55%,transparent 55.5%),radial-gradient(110% 45% at 100% 100%,#c2410c 0 55%,transparent 55.5%),linear-gradient(#fef3c7,#fcd34d)}
.poster-marrakech{background:radial-gradient(circle at 50% 42%,#1d4ed8 0 20%,transparent 20.5%),linear-gradient(to top,#9a3412 0 30%,transparent 30%),linear-gradient(#ffedd5,#fb923c)}
.poster-body{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:14px 16px 16px}
.poster-body h3{font-size:1.05rem;letter-spacing:-.01em;color:#111827}
.poster-body p{font-size:.88rem;color:#4b5563}
.poster-body strong{font-size:.95rem;color:#111827}
.posters-hint{margin-top:10px;font-size:.85rem;color:#4b5563;text-align:center}
@supports selector(::scroll-marker) {
  .posters-hint{display:none}
  .posters-track{scrollbar-width:none;scroll-marker-group:after}
  .posters-track::scroll-button(*){position:absolute;position-anchor:--posters;top:calc(anchor(center) - 50px);width:44px;height:44px;border:0;border-radius:50%;
    background:#fff center/18px no-repeat;box-shadow:0 2px 6px rgba(17,24,39,.12),0 10px 24px rgba(17,24,39,.14);cursor:pointer}
  .posters-track::scroll-button(left){content:"" / "Previous posters";left:calc(anchor(left) - 12px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 18l-6-6 6-6'/%3E%3C/svg%3E")}
  .posters-track::scroll-button(right){content:"" / "Next posters";right:calc(anchor(right) - 12px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E")}
  .posters-track::scroll-button(*):disabled{opacity:0}
  .posters-track::scroll-button(*):focus-visible{outline:3px solid #111827;outline-offset:3px}
  .posters-track::scroll-marker-group{display:flex;justify-content:center;gap:10px;margin-top:14px}
  .poster::scroll-marker{content:"" / attr(aria-label);width:10px;height:10px;border-radius:5px;background:#6b7280;transition:width .3s,background .3s}
  .poster::scroll-marker:target-current{width:26px;background:#111827}
  .poster::scroll-marker:focus-visible{outline:3px solid #111827;outline-offset:3px}
}

/* 3. Scroll-driven coverflow (animation-timeline: view(); Chrome 115+, Safari 26+): each cover turns
   as it crosses the middle of the shelf. Without support, or with reduced motion, the covers sit flat. */
.shelf3d{--cover-w:176px;padding:30px 0 26px;border-radius:24px;background:#111827;color:#f9fafb}
.shelf3d h2{padding:0 28px;font-size:1.2rem;letter-spacing:-.01em}
.shelf3d-sub{padding:4px 28px 0;font-size:.9rem;color:#9ca3af}
.shelf3d-track{display:flex;gap:6px;margin-top:22px;padding:14px calc(50% - var(--cover-w) / 2);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;perspective:900px}
.book{flex:0 0 var(--cover-w);aspect-ratio:2/3;display:flex;flex-direction:column;justify-content:space-between;padding:20px 16px;border-radius:6px 12px 12px 6px;
  scroll-snap-align:center;box-shadow:inset 6px 0 0 rgba(0,0,0,.18),0 18px 30px rgba(0,0,0,.45)}
.book:nth-child(4){scroll-initial-target:nearest}
.book h3{font:600 1.35rem/1.1 Georgia,"Times New Roman",serif}
.book p{font-size:.72rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.book-1{background:radial-gradient(circle at 68% 64%,#5eead4 0 17%,transparent 17.5%),#0f766e;color:#ccfbf1}
.book-2{background:radial-gradient(circle at 30% 28%,#fde68a 0 9%,transparent 9.5%),#1e1b4b;color:#e0e7ff}
.book-3{background:repeating-linear-gradient(100deg,transparent 0 14px,rgba(12,74,110,.18) 14px 16px),#e0f2fe;color:#0c4a6e}
.book-4{background:linear-gradient(135deg,transparent 45%,rgba(120,53,15,.2) 45% 55%,transparent 55%),#fef3c7;color:#78350f}
.book-5{background:radial-gradient(circle at 50% 70%,rgba(255,228,230,.25) 0 30%,transparent 30.5%),#881337;color:#ffe4e6}
.book-6{background:linear-gradient(to top,#bae6fd 0 30%,transparent 30%),#fafaf9;color:#1c1917}
.book-7{background:radial-gradient(120% 40% at 50% 100%,#22c55e 0 50%,transparent 50.5%),#14532d;color:#dcfce7}
.shelf3d-track:focus-visible{outline:3px solid #f9fafb;outline-offset:-3px}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion:no-preference) {
    .book{animation:cover-turn linear both;animation-timeline:view(inline)}
    @keyframes cover-turn{
      0%{transform:rotateY(48deg) scale(.8);filter:brightness(.55)}
      50%{transform:none;filter:none}
      100%{transform:rotateY(-48deg) scale(.8);filter:brightness(.55)}}
  }
}
@media (max-width:760px){.posters{--post-per:1.3;--post-gap:14px}}
@media (max-width:1000px) and (min-width:761px){.posters{--post-per:2.3}}
@media (prefers-reduced-motion:reduce){.tour-track,.tour-slide,.tour-dots label,.tour-dots span,.poster::scroll-marker{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Radio-button carousel (no JavaScript)</span>
<section class="tour" role="region" aria-roledescription="carousel" aria-label="Pennywise tour">
  <div class="tour-view">
    <div class="tour-track">
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="1 of 3">
        <div class="tour-art art-1"></div><h3>Track every expense</h3><p>Snap a receipt and Pennywise files it under the right category before you have put your wallet away.</p>
      </div>
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="2 of 3">
        <div class="tour-art art-2"></div><h3>Split bills in two taps</h3><p>Pick the people and the amount. Everyone gets a link and pays you back from their own banking app.</p>
      </div>
      <div class="tour-slide" role="group" aria-roledescription="slide" aria-label="3 of 3">
        <div class="tour-art art-3"></div><h3>See where it goes</h3><p>A monthly ring shows groceries, rent and everything else at a glance, with no spreadsheets.</p>
      </div>
    </div>
  </div>
  <div class="tour-dots" role="radiogroup" aria-label="Choose a slide">
    <label><input type="radio" name="tour" class="tour-radio" aria-label="Slide 1: Track every expense" checked><span></span></label>
    <label><input type="radio" name="tour" class="tour-radio" aria-label="Slide 2: Split bills in two taps"><span></span></label>
    <label><input type="radio" name="tour" class="tour-radio" aria-label="Slide 3: See where it goes"><span></span></label>
  </div>
</section></div>

<div><span class="lbl">Native scroll buttons and markers</span>
<section class="posters" role="region" aria-roledescription="carousel" aria-label="City posters">
  <div class="posters-track" tabindex="0">
    <div class="poster card" role="group" aria-roledescription="slide" aria-label="1 of 5">
      <div class="poster-art poster-lisbon"></div>
      <div class="poster-body card-body"><div><h3>Lisbon</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster card" role="group" aria-roledescription="slide" aria-label="2 of 5">
      <div class="poster-art poster-kyoto"></div>
      <div class="poster-body card-body"><div><h3>Kyoto</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster card" role="group" aria-roledescription="slide" aria-label="3 of 5">
      <div class="poster-art poster-reykjavik"></div>
      <div class="poster-body card-body"><div><h3>Reykjavík</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster card" role="group" aria-roledescription="slide" aria-label="4 of 5">
      <div class="poster-art poster-oaxaca"></div>
      <div class="poster-body card-body"><div><h3>Oaxaca</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
    <div class="poster card" role="group" aria-roledescription="slide" aria-label="5 of 5">
      <div class="poster-art poster-marrakech"></div>
      <div class="poster-body card-body"><div><h3>Marrakech</h3><p>Giclée print · A3</p></div><strong>$28</strong></div>
    </div>
  </div>
  <p class="posters-hint">Scroll or swipe sideways for all five posters.</p>
</section></div>

<div><span class="lbl">Scroll-driven coverflow</span>
<section class="shelf3d" role="region" aria-roledescription="carousel" aria-label="Staff picks">
  <h2>Staff picks for October</h2><p class="shelf3d-sub">Seven novels our booksellers could not put down</p>
  <div class="shelf3d-track" tabindex="0">
    <div class="book book-1" role="group" aria-roledescription="slide" aria-label="1 of 7"><h3>The Salt Road</h3><p>Mara Ellison</p></div>
    <div class="book book-2" role="group" aria-roledescription="slide" aria-label="2 of 7"><h3>Small Hours</h3><p>Idris Coleman</p></div>
    <div class="book book-3" role="group" aria-roledescription="slide" aria-label="3 of 7"><h3>A Map of Rain</h3><p>June Okafor</p></div>
    <div class="book book-4" role="group" aria-roledescription="slide" aria-label="4 of 7"><h3>Glasshouse</h3><p>Theo Varga</p></div>
    <div class="book book-5" role="group" aria-roledescription="slide" aria-label="5 of 7"><h3>The Quiet Year</h3><p>Ines Duarte</p></div>
    <div class="book book-6" role="group" aria-roledescription="slide" aria-label="6 of 7"><h3>Paper Boats</h3><p>Kenji Mori</p></div>
    <div class="book book-7" role="group" aria-roledescription="slide" aria-label="7 of 7"><h3>Low Tide</h3><p>Ada Brennan</p></div>
  </div>
</section></div>
CSS
/* Bootstrap version: Bootstrap .card and utilities, with the same modern-CSS rules. None of these needs Bootstrap's JavaScript. */
/* 1. Radio carousel: the dots are radio buttons, so arrow keys move between slides and no script is needed.
   :has() reads which radio is checked and slides the track. Works in every current browser. */
.tour{--tour-accent:#0f766e;max-width:440px;margin:0 auto;padding:22px;border-radius:24px;background:#fff;box-shadow:0 18px 40px rgba(17,24,39,.08)}
.tour-view{overflow:hidden;border-radius:18px}
.tour-track{display:flex;transform:translateX(calc(var(--i,0) * -100%));transition:transform .45s cubic-bezier(.2,.7,.2,1)}
.tour:has(.tour-dots label:nth-child(2) :checked){--i:1}
.tour:has(.tour-dots label:nth-child(3) :checked){--i:2}
.tour-slide{flex:0 0 100%;visibility:hidden;transition:visibility 0s .45s}
.tour:has(.tour-dots label:nth-child(1) :checked) .tour-slide:nth-child(1),
.tour:has(.tour-dots label:nth-child(2) :checked) .tour-slide:nth-child(2),
.tour:has(.tour-dots label:nth-child(3) :checked) .tour-slide:nth-child(3){visibility:visible;transition-delay:0s}
.tour-art{height:220px;border-radius:18px}
.art-1{background:repeating-linear-gradient(to bottom,#fff 0 10px,transparent 10px 22px) 50% 55%/46% 52% no-repeat,linear-gradient(#0f766e,#0f766e) 50% 55%/56% 64% no-repeat,radial-gradient(circle at 80% 20%,#99f6e4 0 12%,transparent 12.5%),linear-gradient(135deg,#ccfbf1,#5eead4)}
.art-2{background:radial-gradient(circle at 40% 50%,rgba(15,118,110,.9) 0 22%,transparent 22.5%),radial-gradient(circle at 60% 50%,rgba(251,191,36,.9) 0 22%,transparent 22.5%),linear-gradient(135deg,#fef3c7,#fde68a)}
.art-3{background:radial-gradient(circle at 50% 50%,#e0f2fe 0 16%,transparent 16.5%),radial-gradient(circle at 50% 50%,transparent 0 32%,#e0f2fe 32.5%),conic-gradient(from 20deg at 50% 50%,#0f766e 0 38%,#f59e0b 0 62%,#6366f1 0 80%,#e11d48 0 100%)}
.tour-slide h3{margin-top:22px;font-size:1.35rem;letter-spacing:-.02em;color:#111827}
.tour-slide p{margin-top:8px;line-height:1.6;color:#4b5563}
.tour-dots{display:flex;justify-content:center;gap:2px;margin-top:20px}
.tour-dots label{position:relative;width:24px;height:24px;display:grid;place-items:center;cursor:pointer;transition:width .3s}
.tour-dots input{position:absolute;inset:0;margin:0;opacity:0;cursor:pointer}
.tour-dots span{width:10px;height:10px;border-radius:5px;background:#6b7280;transition:width .3s,background .3s}
.tour-dots label:has(:checked){width:38px}
.tour-dots label:has(:checked) span{width:28px;background:var(--tour-accent)}
.tour-dots input:focus-visible + span{outline:3px solid var(--tour-accent);outline-offset:3px}

/* 2. Native scroll buttons and markers (Chrome and Edge 135+): the browser generates the arrows and
   the dots from CSS. Elsewhere it is a plain swipeable scroll-snap row with a scrollbar. */
.posters{--post-gap:18px;--post-per:3;position:relative}
.posters-track{display:flex;gap:var(--post-gap);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;padding-bottom:6px;anchor-name:--posters}
.poster{flex:0 0 calc((100% - (var(--post-per) - 1) * var(--post-gap)) / var(--post-per));scroll-snap-align:start;border-radius:16px;overflow:hidden;background:#fff;
  box-shadow:0 1px 2px rgba(17,24,39,.06),0 10px 24px rgba(17,24,39,.06)}
.poster-art{aspect-ratio:4/5}
.poster-lisbon{background:radial-gradient(circle at 30% 32%,#ea580c 0 15%,transparent 15.5%),radial-gradient(120% 60% at 80% 100%,#9a3412 0 50%,transparent 50.5%),linear-gradient(to top,#0e7490 0 22%,transparent 22%),linear-gradient(#fde68a,#fdba74)}
.poster-kyoto{background:radial-gradient(circle at 68% 34%,#e11d48 0 14%,transparent 14.5%),radial-gradient(90% 50% at 15% 100%,#4c0519 0 55%,transparent 55.5%),radial-gradient(90% 45% at 90% 100%,#881337 0 55%,transparent 55.5%),linear-gradient(#ffe4e6,#fbcfe8)}
.poster-reykjavik{background:radial-gradient(70% 16% at 40% 30%,rgba(52,211,153,.85),transparent 70%),radial-gradient(60% 12% at 65% 40%,rgba(167,139,250,.7),transparent 70%),radial-gradient(120% 55% at 50% 108%,#e2e8f0 0 45%,transparent 45.5%),linear-gradient(#0f172a,#1e3a8a)}
.poster-oaxaca{background:radial-gradient(circle at 50% 38%,#dc2626 0 18%,transparent 18.5%),radial-gradient(110% 50% at 0% 100%,#7c2d12 0 55%,transparent 55.5%),radial-gradient(110% 45% at 100% 100%,#c2410c 0 55%,transparent 55.5%),linear-gradient(#fef3c7,#fcd34d)}
.poster-marrakech{background:radial-gradient(circle at 50% 42%,#1d4ed8 0 20%,transparent 20.5%),linear-gradient(to top,#9a3412 0 30%,transparent 30%),linear-gradient(#ffedd5,#fb923c)}
.poster-body{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:14px 16px 16px}
.poster-body h3{font-size:1.05rem;letter-spacing:-.01em;color:#111827}
.poster-body p{font-size:.88rem;color:#4b5563}
.poster-body strong{font-size:.95rem;color:#111827}
.posters-hint{margin-top:10px;font-size:.85rem;color:#4b5563;text-align:center}
@supports selector(::scroll-marker) {
  .posters-hint{display:none}
  .posters-track{scrollbar-width:none;scroll-marker-group:after}
  .posters-track::scroll-button(*){position:absolute;position-anchor:--posters;top:calc(anchor(center) - 50px);width:44px;height:44px;border:0;border-radius:50%;
    background:#fff center/18px no-repeat;box-shadow:0 2px 6px rgba(17,24,39,.12),0 10px 24px rgba(17,24,39,.14);cursor:pointer}
  .posters-track::scroll-button(left){content:"" / "Previous posters";left:calc(anchor(left) - 12px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 18l-6-6 6-6'/%3E%3C/svg%3E")}
  .posters-track::scroll-button(right){content:"" / "Next posters";right:calc(anchor(right) - 12px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E")}
  .posters-track::scroll-button(*):disabled{opacity:0}
  .posters-track::scroll-button(*):focus-visible{outline:3px solid #111827;outline-offset:3px}
  .posters-track::scroll-marker-group{display:flex;justify-content:center;gap:10px;margin-top:14px}
  .poster::scroll-marker{content:"" / attr(aria-label);width:10px;height:10px;border-radius:5px;background:#6b7280;transition:width .3s,background .3s}
  .poster::scroll-marker:target-current{width:26px;background:#111827}
  .poster::scroll-marker:focus-visible{outline:3px solid #111827;outline-offset:3px}
}

/* 3. Scroll-driven coverflow (animation-timeline: view(); Chrome 115+, Safari 26+): each cover turns
   as it crosses the middle of the shelf. Without support, or with reduced motion, the covers sit flat. */
.shelf3d{--cover-w:176px;padding:30px 0 26px;border-radius:24px;background:#111827;color:#f9fafb}
.shelf3d h2{padding:0 28px;font-size:1.2rem;letter-spacing:-.01em}
.shelf3d-sub{padding:4px 28px 0;font-size:.9rem;color:#9ca3af}
.shelf3d-track{display:flex;gap:6px;margin-top:22px;padding:14px calc(50% - var(--cover-w) / 2);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;perspective:900px}
.book{flex:0 0 var(--cover-w);aspect-ratio:2/3;display:flex;flex-direction:column;justify-content:space-between;padding:20px 16px;border-radius:6px 12px 12px 6px;
  scroll-snap-align:center;box-shadow:inset 6px 0 0 rgba(0,0,0,.18),0 18px 30px rgba(0,0,0,.45)}
.book:nth-child(4){scroll-initial-target:nearest}
.book h3{font:600 1.35rem/1.1 Georgia,"Times New Roman",serif}
.book p{font-size:.72rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.book-1{background:radial-gradient(circle at 68% 64%,#5eead4 0 17%,transparent 17.5%),#0f766e;color:#ccfbf1}
.book-2{background:radial-gradient(circle at 30% 28%,#fde68a 0 9%,transparent 9.5%),#1e1b4b;color:#e0e7ff}
.book-3{background:repeating-linear-gradient(100deg,transparent 0 14px,rgba(12,74,110,.18) 14px 16px),#e0f2fe;color:#0c4a6e}
.book-4{background:linear-gradient(135deg,transparent 45%,rgba(120,53,15,.2) 45% 55%,transparent 55%),#fef3c7;color:#78350f}
.book-5{background:radial-gradient(circle at 50% 70%,rgba(255,228,230,.25) 0 30%,transparent 30.5%),#881337;color:#ffe4e6}
.book-6{background:linear-gradient(to top,#bae6fd 0 30%,transparent 30%),#fafaf9;color:#1c1917}
.book-7{background:radial-gradient(120% 40% at 50% 100%,#22c55e 0 50%,transparent 50.5%),#14532d;color:#dcfce7}
.shelf3d-track:focus-visible{outline:3px solid #f9fafb;outline-offset:-3px}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion:no-preference) {
    .book{animation:cover-turn linear both;animation-timeline:view(inline)}
    @keyframes cover-turn{
      0%{transform:rotateY(48deg) scale(.8);filter:brightness(.55)}
      50%{transform:none;filter:none}
      100%{transform:rotateY(-48deg) scale(.8);filter:brightness(.55)}}
  }
}
@media (max-width:760px){.posters{--post-per:1.3;--post-gap:14px}}
@media (max-width:1000px) and (min-width:761px){.posters{--post-per:2.3}}
@media (prefers-reduced-motion:reduce){.tour-track,.tour-slide,.tour-dots label,.tour-dots span,.poster::scroll-marker{transition:none}}
/* Bootstrap resets: headings and paragraphs inside the components carry no margins */
.tour h3,.tour p,.posters h3,.posters p,.shelf3d h2,.shelf3d h3,.shelf3d p{margin-bottom:0}
.tour-slide h3,.poster-body h3,.book h3,.shelf3d h2{font-weight:700}
.book h3{font-weight:600}
.tour-dots label{margin:0}
.poster.card{--bs-card-border-width:0;--bs-card-border-radius:16px}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5563] uppercase">Radio-button carousel (no JavaScript)</span>
<section class="group/tour mx-auto max-w-[440px] rounded-3xl bg-white p-[22px] shadow-[0_18px_40px_rgba(17,24,39,.08)] has-[label:nth-child(2)_:checked]:[--i:1] has-[label:nth-child(3)_:checked]:[--i:2]" role="region" aria-roledescription="carousel" aria-label="Pennywise tour">
  <div class="overflow-hidden rounded-[18px]">
    <div class="flex translate-x-[calc(var(--i,0)*-100%)] transition-transform duration-450 ease-[cubic-bezier(.2,.7,.2,1)] motion-reduce:transition-none">
      <div class="invisible shrink-0 basis-full transition-[visibility] delay-450 duration-0 group-has-[label:nth-child(1)_:checked]/tour:visible group-has-[label:nth-child(1)_:checked]/tour:delay-0" role="group" aria-roledescription="slide" aria-label="1 of 3">
        <div class="h-[220px] rounded-[18px] [background:repeating-linear-gradient(to_bottom,#fff_0_10px,transparent_10px_22px)_50%_55%/46%_52%_no-repeat,linear-gradient(#0f766e,#0f766e)_50%_55%/56%_64%_no-repeat,radial-gradient(circle_at_80%_20%,#99f6e4_0_12%,transparent_12.5%),linear-gradient(135deg,#ccfbf1,#5eead4)]"></div><h3 class="mt-[22px] text-[1.35rem] font-bold tracking-[-.02em]">Track every expense</h3><p class="mt-2 leading-[1.6] text-[#4b5563]">Snap a receipt and Pennywise files it under the right category before you have put your wallet away.</p>
      </div>
      <div class="invisible shrink-0 basis-full transition-[visibility] delay-450 duration-0 group-has-[label:nth-child(2)_:checked]/tour:visible group-has-[label:nth-child(2)_:checked]/tour:delay-0" role="group" aria-roledescription="slide" aria-label="2 of 3">
        <div class="h-[220px] rounded-[18px] [background:radial-gradient(circle_at_40%_50%,rgba(15,118,110,.9)_0_22%,transparent_22.5%),radial-gradient(circle_at_60%_50%,rgba(251,191,36,.9)_0_22%,transparent_22.5%),linear-gradient(135deg,#fef3c7,#fde68a)]"></div><h3 class="mt-[22px] text-[1.35rem] font-bold tracking-[-.02em]">Split bills in two taps</h3><p class="mt-2 leading-[1.6] text-[#4b5563]">Pick the people and the amount. Everyone gets a link and pays you back from their own banking app.</p>
      </div>
      <div class="invisible shrink-0 basis-full transition-[visibility] delay-450 duration-0 group-has-[label:nth-child(3)_:checked]/tour:visible group-has-[label:nth-child(3)_:checked]/tour:delay-0" role="group" aria-roledescription="slide" aria-label="3 of 3">
        <div class="h-[220px] rounded-[18px] [background:radial-gradient(circle_at_50%_50%,#e0f2fe_0_16%,transparent_16.5%),radial-gradient(circle_at_50%_50%,transparent_0_32%,#e0f2fe_32.5%),conic-gradient(from_20deg_at_50%_50%,#0f766e_0_38%,#f59e0b_0_62%,#6366f1_0_80%,#e11d48_0_100%)]"></div><h3 class="mt-[22px] text-[1.35rem] font-bold tracking-[-.02em]">See where it goes</h3><p class="mt-2 leading-[1.6] text-[#4b5563]">A monthly ring shows groceries, rent and everything else at a glance, with no spreadsheets.</p>
      </div>
    </div>
  </div>
  <div class="mt-5 flex justify-center gap-0.5" role="radiogroup" aria-label="Choose a slide">
    <label class="relative grid size-6 cursor-pointer place-items-center transition-[width] duration-300 has-checked:w-[38px] motion-reduce:transition-none"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="tour" aria-label="Slide 1: Track every expense" checked><span class="h-2.5 w-2.5 rounded-[5px] bg-[#6b7280] transition-[width,background-color] duration-300 peer-checked:w-7 peer-checked:bg-tour peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tour motion-reduce:transition-none"></span></label>
    <label class="relative grid size-6 cursor-pointer place-items-center transition-[width] duration-300 has-checked:w-[38px] motion-reduce:transition-none"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="tour" aria-label="Slide 2: Split bills in two taps"><span class="h-2.5 w-2.5 rounded-[5px] bg-[#6b7280] transition-[width,background-color] duration-300 peer-checked:w-7 peer-checked:bg-tour peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tour motion-reduce:transition-none"></span></label>
    <label class="relative grid size-6 cursor-pointer place-items-center transition-[width] duration-300 has-checked:w-[38px] motion-reduce:transition-none"><input class="peer absolute inset-0 m-0 cursor-pointer opacity-0" type="radio" name="tour" aria-label="Slide 3: See where it goes"><span class="h-2.5 w-2.5 rounded-[5px] bg-[#6b7280] transition-[width,background-color] duration-300 peer-checked:w-7 peer-checked:bg-tour peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tour motion-reduce:transition-none"></span></label>
  </div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5563] uppercase">Native scroll buttons and markers</span>
<section class="relative [--gap:14px] [--per:1.3] min-[761px]:[--gap:18px] min-[761px]:[--per:2.3] min-[1001px]:[--per:3]" role="region" aria-roledescription="carousel" aria-label="City posters">
  <div class="flex snap-x snap-mandatory gap-(--gap) overflow-x-auto overscroll-x-contain pb-1.5" data-posters tabindex="0">
    <div class="shrink-0 basis-[calc((100%-(var(--per)-1)*var(--gap))/var(--per))] snap-start overflow-hidden rounded-2xl bg-white shadow-[0_1px_2px_rgba(17,24,39,.06),0_10px_24px_rgba(17,24,39,.06)]" role="group" aria-roledescription="slide" aria-label="1 of 5">
      <div class="aspect-[4/5] bg-[radial-gradient(circle_at_30%_32%,#ea580c_0_15%,transparent_15.5%),radial-gradient(120%_60%_at_80%_100%,#9a3412_0_50%,transparent_50.5%),linear-gradient(to_top,#0e7490_0_22%,transparent_22%),linear-gradient(#fde68a,#fdba74)]"></div>
      <div class="flex items-baseline justify-between gap-2.5 px-4 pt-3.5 pb-4"><div><h3 class="text-[1.05rem] font-bold tracking-[-.01em]">Lisbon</h3><p class="text-[.88rem] text-[#4b5563]">Giclée print · A3</p></div><strong class="text-[.95rem]">$28</strong></div>
    </div>
    <div class="shrink-0 basis-[calc((100%-(var(--per)-1)*var(--gap))/var(--per))] snap-start overflow-hidden rounded-2xl bg-white shadow-[0_1px_2px_rgba(17,24,39,.06),0_10px_24px_rgba(17,24,39,.06)]" role="group" aria-roledescription="slide" aria-label="2 of 5">
      <div class="aspect-[4/5] bg-[radial-gradient(circle_at_68%_34%,#e11d48_0_14%,transparent_14.5%),radial-gradient(90%_50%_at_15%_100%,#4c0519_0_55%,transparent_55.5%),radial-gradient(90%_45%_at_90%_100%,#881337_0_55%,transparent_55.5%),linear-gradient(#ffe4e6,#fbcfe8)]"></div>
      <div class="flex items-baseline justify-between gap-2.5 px-4 pt-3.5 pb-4"><div><h3 class="text-[1.05rem] font-bold tracking-[-.01em]">Kyoto</h3><p class="text-[.88rem] text-[#4b5563]">Giclée print · A3</p></div><strong class="text-[.95rem]">$28</strong></div>
    </div>
    <div class="shrink-0 basis-[calc((100%-(var(--per)-1)*var(--gap))/var(--per))] snap-start overflow-hidden rounded-2xl bg-white shadow-[0_1px_2px_rgba(17,24,39,.06),0_10px_24px_rgba(17,24,39,.06)]" role="group" aria-roledescription="slide" aria-label="3 of 5">
      <div class="aspect-[4/5] bg-[radial-gradient(70%_16%_at_40%_30%,rgba(52,211,153,.85),transparent_70%),radial-gradient(60%_12%_at_65%_40%,rgba(167,139,250,.7),transparent_70%),radial-gradient(120%_55%_at_50%_108%,#e2e8f0_0_45%,transparent_45.5%),linear-gradient(#0f172a,#1e3a8a)]"></div>
      <div class="flex items-baseline justify-between gap-2.5 px-4 pt-3.5 pb-4"><div><h3 class="text-[1.05rem] font-bold tracking-[-.01em]">Reykjavík</h3><p class="text-[.88rem] text-[#4b5563]">Giclée print · A3</p></div><strong class="text-[.95rem]">$28</strong></div>
    </div>
    <div class="shrink-0 basis-[calc((100%-(var(--per)-1)*var(--gap))/var(--per))] snap-start overflow-hidden rounded-2xl bg-white shadow-[0_1px_2px_rgba(17,24,39,.06),0_10px_24px_rgba(17,24,39,.06)]" role="group" aria-roledescription="slide" aria-label="4 of 5">
      <div class="aspect-[4/5] bg-[radial-gradient(circle_at_50%_38%,#dc2626_0_18%,transparent_18.5%),radial-gradient(110%_50%_at_0%_100%,#7c2d12_0_55%,transparent_55.5%),radial-gradient(110%_45%_at_100%_100%,#c2410c_0_55%,transparent_55.5%),linear-gradient(#fef3c7,#fcd34d)]"></div>
      <div class="flex items-baseline justify-between gap-2.5 px-4 pt-3.5 pb-4"><div><h3 class="text-[1.05rem] font-bold tracking-[-.01em]">Oaxaca</h3><p class="text-[.88rem] text-[#4b5563]">Giclée print · A3</p></div><strong class="text-[.95rem]">$28</strong></div>
    </div>
    <div class="shrink-0 basis-[calc((100%-(var(--per)-1)*var(--gap))/var(--per))] snap-start overflow-hidden rounded-2xl bg-white shadow-[0_1px_2px_rgba(17,24,39,.06),0_10px_24px_rgba(17,24,39,.06)]" role="group" aria-roledescription="slide" aria-label="5 of 5">
      <div class="aspect-[4/5] bg-[radial-gradient(circle_at_50%_42%,#1d4ed8_0_20%,transparent_20.5%),linear-gradient(to_top,#9a3412_0_30%,transparent_30%),linear-gradient(#ffedd5,#fb923c)]"></div>
      <div class="flex items-baseline justify-between gap-2.5 px-4 pt-3.5 pb-4"><div><h3 class="text-[1.05rem] font-bold tracking-[-.01em]">Marrakech</h3><p class="text-[.88rem] text-[#4b5563]">Giclée print · A3</p></div><strong class="text-[.95rem]">$28</strong></div>
    </div>
  </div>
  <p class="mt-2.5 text-center text-[.85rem] text-[#4b5563]" data-posters-hint>Scroll or swipe sideways for all five posters.</p>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5563] uppercase">Scroll-driven coverflow</span>
<section class="rounded-3xl bg-[#111827] pt-[30px] pb-[26px] text-[#f9fafb]" role="region" aria-roledescription="carousel" aria-label="Staff picks">
  <h2 class="px-7 text-[1.2rem] font-bold tracking-[-.01em]">Staff picks for October</h2><p class="px-7 pt-1 text-[.9rem] text-[#9ca3af]">Seven novels our booksellers could not put down</p>
  <div class="mt-[22px] flex snap-x snap-mandatory gap-1.5 overflow-x-auto overscroll-x-contain px-[calc(50%-88px)] py-3.5 [perspective:900px] focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-[#f9fafb]" data-coverflow tabindex="0">
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#ccfbf1] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:radial-gradient(circle_at_68%_64%,#5eead4_0_17%,transparent_17.5%),#0f766e]" role="group" aria-roledescription="slide" aria-label="1 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">The Salt Road</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Mara Ellison</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#e0e7ff] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:radial-gradient(circle_at_30%_28%,#fde68a_0_9%,transparent_9.5%),#1e1b4b]" role="group" aria-roledescription="slide" aria-label="2 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">Small Hours</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Idris Coleman</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#0c4a6e] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:repeating-linear-gradient(100deg,transparent_0_14px,rgba(12,74,110,.18)_14px_16px),#e0f2fe]" role="group" aria-roledescription="slide" aria-label="3 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">A Map of Rain</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">June Okafor</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#78350f] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:linear-gradient(135deg,transparent_45%,rgba(120,53,15,.2)_45%_55%,transparent_55%),#fef3c7] [scroll-initial-target:nearest]" role="group" aria-roledescription="slide" aria-label="4 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">Glasshouse</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Theo Varga</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#ffe4e6] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:radial-gradient(circle_at_50%_70%,rgba(255,228,230,.25)_0_30%,transparent_30.5%),#881337]" role="group" aria-roledescription="slide" aria-label="5 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">The Quiet Year</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Ines Duarte</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#1c1917] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:linear-gradient(to_top,#bae6fd_0_30%,transparent_30%),#fafaf9]" role="group" aria-roledescription="slide" aria-label="6 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">Paper Boats</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Kenji Mori</p></div>
    <div class="flex aspect-[2/3] w-44 shrink-0 snap-center flex-col justify-between rounded-l-md rounded-r-xl px-4 py-5 text-[#dcfce7] shadow-[inset_6px_0_0_rgba(0,0,0,.18),0_18px_30px_rgba(0,0,0,.45)] [background:radial-gradient(120%_40%_at_50%_100%,#22c55e_0_50%,transparent_50.5%),#14532d]" role="group" aria-roledescription="slide" aria-label="7 of 7"><h3 class="font-[Georgia,'Times_New_Roman',serif] text-[1.35rem] leading-[1.1] font-semibold">Low Tide</h3><p class="text-[.72rem] font-bold tracking-[.14em] uppercase">Ada Brennan</p></div>
  </div>
</section></div>
CSS
@theme {
  --color-tour: #0f766e;
}
/* Native scroll buttons and markers (Chrome and Edge 135+). Elsewhere the row is a plain swipeable scroll-snap track. */
@supports selector(::scroll-marker) {
  [data-posters-hint] { display: none; }
  [data-posters] { scrollbar-width: none; scroll-marker-group: after; anchor-name: --posters; }
  [data-posters]::scroll-button(*) { position: absolute; position-anchor: --posters; top: calc(anchor(center) - 50px); width: 44px; height: 44px; border: 0; border-radius: 50%;
    background: #fff center / 18px no-repeat; box-shadow: 0 2px 6px rgba(17,24,39,.12), 0 10px 24px rgba(17,24,39,.14); cursor: pointer; }
  [data-posters]::scroll-button(left) { content: "" / "Previous posters"; left: calc(anchor(left) - 12px); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 18l-6-6 6-6'/%3E%3C/svg%3E"); }
  [data-posters]::scroll-button(right) { content: "" / "Next posters"; right: calc(anchor(right) - 12px); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E"); }
  [data-posters]::scroll-button(*):disabled { opacity: 0; }
  [data-posters]::scroll-button(*):focus-visible { outline: 3px solid #111827; outline-offset: 3px; }
  [data-posters]::scroll-marker-group { display: flex; justify-content: center; gap: 10px; margin-top: 14px; }
  [data-posters] > *::scroll-marker { content: "" / attr(aria-label); width: 10px; height: 10px; border-radius: 5px; background: #6b7280; transition: width .3s, background .3s; }
  [data-posters] > *::scroll-marker:target-current { width: 26px; background: #111827; }
  [data-posters] > *::scroll-marker:focus-visible { outline: 3px solid #111827; outline-offset: 3px; }
  @media (prefers-reduced-motion: reduce) { [data-posters] > *::scroll-marker { transition: none; } }
}
/* Scroll-driven coverflow (Chrome 115+, Safari 26+): each cover turns as it crosses the middle */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-coverflow] > * { animation: cover-turn linear both; animation-timeline: view(inline); }
    @keyframes cover-turn {
      0% { transform: rotateY(48deg) scale(.8); filter: brightness(.55); }
      50% { transform: none; filter: none; }
      100% { transform: rotateY(-48deg) scale(.8); filter: brightness(.55); }
    }
  }
}

About this set

Three carousels that need no JavaScript at all, built on newer CSS. Radio-button carousel is a three-step app onboarding card: the dots are real radio buttons, :has() reads which one is checked and slides the track, and the arrow keys move between slides the way they do in any radio group. It works in every current browser. Native scroll buttons and markers is a row of five city posters whose arrows and dots are generated by the browser from ::scroll-button() and ::scroll-marker, with :target-current marking the current dot and anchor positioning placing the arrows. That works in Chrome and Edge 135 and later; other browsers get a swipeable scroll-snap row and a short hint to scroll sideways. Scroll-driven coverflow is a shelf of seven book covers drawn in CSS that turn in 3D as they cross the middle, using animation-timeline: view(), and it opens on the middle cover with scroll-initial-target. Where scroll-driven animations are missing, or reduced motion is set, the covers simply sit flat on a normal scroll-snap row.

What’s included

  • Radio-button carousel driven by :has(), with arrow-key support
  • Browser-generated arrows and dots from ::scroll-button() and ::scroll-marker
  • 3D coverflow driven by animation-timeline: view()
  • Illustrations drawn with CSS gradients, no image files
  • Every part falls back to a plain scroll-snap row

Accessibility

Each carousel is a labelled region with slides labelled "n of N". The radio dots sit in a radiogroup named "Choose a slide", each radio named after its slide, and slides that are not showing are set to visibility: hidden so they drop out of the tab order. The generated scroll buttons get their names, "Previous posters" and "Next posters", from CSS alt text, and each marker takes its slide's label. Focus outlines are drawn on the visible dot, button or marker, and the slide transition and coverflow are switched off under prefers-reduced-motion.

Customise it

Change --tour-accent on .tour, --post-per and --post-gap on .posters, and --cover-w on .shelf3d; the artwork lives in the .tour-art, .poster-* and .book-* backgrounds. In Tailwind the same values sit in arbitrary properties and [background:] utilities, with only the pseudo-element and scroll-timeline rules in the style block.