A native horizontal swipe gallery using CSS scroll-snap. Zero JS. Feels like a native iOS picker.
flex-basis low enough that the user always sees a sliver of the next item — a strong visual cue that there's more.
Flex row with overflow-x: auto + scroll-snap-type: x mandatory on the parent and scroll-snap-align: center on each child. The browser handles the snap behavior natively. Set children to flex: 0 0 85% on phone so a sliver of the next item shows — that's the affordance that says "swipe me."
<div class="reel">
<div class="slide-card">Villa 1</div>
<div class="slide-card">Villa 2</div>
<div class="slide-card">Villa 3</div>
</div>
.reel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 12px;
-webkit-overflow-scrolling: touch;
}
.slide-card {
flex: 0 0 85%; /* phone: show sliver of next */
scroll-snap-align: center;
}
@media (min-width: 720px) {
.slide-card { flex-basis: 42%; } /* desktop: two cards visible */
}
Full-width slides — set flex-basis: 100% if you want one item per viewport. Pair with dot indicators below using radio inputs + :target for navigation (or accept "no nav" if pure swipe is enough).
Vertical scroll-snap on desktop — Peñaranda y Cobo uses this pattern: vertical scroll-snap reel on desktop, horizontal on phone. Different axis matches each device's natural gesture. See vault/Topics/web-patterns.md § Pattern 24 (Cinema reel).
Snap on the edge — change scroll-snap-align: center to start for a different feel. Center is more "carousel-y" — cards sit centered. Start is more "list-y" — items line up from the left.
Compatibility: scroll-snap supported in all modern browsers. · Code playbook cross-ref: vault/Topics/web-patterns.md § Pattern 24 (Cinema reel).