Gallery✨ Premium

Card Carousel

A row of cards that moves one card at a time, with the arrows or a finger, and stops dead once the last card is fully visible.

CSSMultiScroll

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →

3 usage examples

"You may also like" under a novel's page — Card Carousel example 1

① "You may also like" under a novel's page

WhenOn the Lisoranne online bookshop, the page of "The Tide House" (cover, summary, Add to basket button) ends with a row of eight suggested novel covers, title at the bottom of each. The arrows move the selection one cover at a time.
WhyThe suggestions take a single line under the page instead of a full grid, and moving card by card keeps each cover readable while it travels. The stop at the end of the row says clearly that nothing comes after.
Settings--ccr-max-width: none, 92 × 138 px cards (--ccr-card-w, --ccr-card-h), a cover <img> + an HTML title in each card, dark arrows (scene CSS). In the capture the demo clicks the real arrow every 0.9 s and goes back at the end of the row.
A climbing gym's plans on mobile — Card Carousel example 2

② A climbing gym's plans on mobile

WhenOn the Grimpevelle gym Prices page, seen on a phone: four plan cards (taster, 10-pass, annual, student) with price, terms and a Choose button. One card nearly fills the screen and the edge of the next one shows: you swipe with your thumb to compare.
WhyOn a small screen four price columns would be unreadable; as a carousel each plan keeps its normal size, and the visible edge of the next card invites the gesture. The highlighted plan (annual, in orange) is still a card like the others.
Settings128 × 206 px cards, 10 px gap (--ccr-gap), arrows hidden (scene CSS) since the gesture is enough on mobile. The capture sends the real code a touch pointerdown then pointerup every second and reverses at the end of the row; the white dot is a scene marker.
A strip of dates in a booking widget — Card Carousel example 3

③ A strip of dates in a booking widget

WhenOn the Le Tavelin restaurant page, the "Book a table" widget shows a small row of days (weekday, date, free tables or "full"), with 22 px arrows on each side; below, times, number of guests and the Book button.
WhyThe effect becomes a compact picker: ten days fit in 160 px, and the stop at the end of the period avoids suggesting there are more dates. Used as a small detail, it takes no more room than a form field.
Settings44 × 58 px cards, 6 px gap, arrows shrunk to 22 px and placed outside the track (26 px margins), selected day in brick red (scene CSS). The demo clicks the real arrow every 0.8 s in the capture.

How it works

The .fx-0166-root root holds a .ccr-slider: a .ccr-viewport window that hides what overflows, a flex .ccr-track with a 12 px gap, and two round frosted-glass arrows on the edges. Each .ccr-card is 140 × 180 px by default and grows by 5% on hover or when something inside it has focus.

The step is not hard-coded: on load and on every size change (ResizeObserver), the script measures the real width of a card and the track gap, then the maximum distance needed for the last card to reach the right edge. An arrow or a drag of more than 50 px shifts the track by one step (translateX, 0.4 s); at the end the track stops to the pixel and the matching arrow switches to aria-disabled="true" (35% opacity). No looping.

The carousel takes its container's width (640 px at most by default): the wider it is, the more cards it shows and the fewer steps there are. An <img> placed in a card covers it; text, a price or a button work too. Each .fx-0166-root on the page has its own instance.

Accessibility

  • Named arrows: real "Previous cards" / "Next cards" buttons, flagged aria-disabled at the end of the row, with a :focus-visible outline.
  • Off-screen cards: any card that isn't fully visible becomes inert; a link inside it cannot take focus and make the row jump.
  • Roles: a named aria-roledescription="carousel" region, cards are role="group" announced as "card, 3 of 6" (texts in the HTML, to translate).
  • Reduced motion: under prefers-reduced-motion: reduce, the row and the hover zoom change without transitions.
  • Touch: Pointer Events and touch-action: pan-y: you swipe the row horizontally without blocking the page's vertical scrolling.

Browser compatibility

Dependency-free JavaScript: ResizeObserver (Chrome 64, Firefox 69, Safari 13.1), Pointer Events, inert (Chrome 102, Firefox 112, Safari 15.5 according to MDN). Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two rows with only the second one driven, end stop, reduced motion, 390 px without overflow, finger swipe (Chromium) without blocking vertical scrolling, 0 errors.

Chrome 102+✓ Full (measured)
Firefox 112+✓ Full (measured)
Safari 15.5+✓ Full (measured in WebKit)
Edge 102+✓ Full (Chromium engine)
Mobile iOS 15.5+✓ WebKit engine, not checked on a device
Android Chrome 102+✓ Full (touch measured)

Without JavaScript, the row shows the first cards and the arrows do nothing. Before Safari 15.5 / Firefox 112, inert is ignored: navigation works, but an off-screen card can receive focus.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0166-root">
  <div class="ccr-slider" role="region" aria-roledescription="carousel" aria-label="…">
    <div class="ccr-viewport">
      <div class="ccr-track">
        <div class="ccr-card" role="group" aria-roledescription="card" aria-label="1 of 6">…</div>
        …
      </div>
    </div>
    <button class="ccr-arrow ccr-prev" type="button" aria-label="…">…</button>
    <button class="ccr-arrow ccr-next" type="button" aria-label="…">…</button>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Maximum width (CSS — --ccr-max-width) 640px The row takes its container's width up to this limit. none for the full width.
Card size (CSS — --ccr-card-w / --ccr-card-h) 140px / 180px The scroll step automatically follows the cards' real width.
Gap between cards (CSS — --ccr-gap) 12px Added to the step: an arrow always moves by exactly one card and one gap.
Card content (HTML — .ccr-card) 6 gradients "Card 1" to "Card 6" Image (an <img> covering the card), text, price, button. Update the "X of N" aria-label if you add some.
Hover zoom (CSS — .ccr-card:hover) scale(1.05), 0.3 s Remove the rule if your cards hold text that must stay still.
Inactive arrow (CSS — .ccr-arrow[aria-disabled="true"]) opacity .35 Look of the arrow at the end of the row; use display:none to hide it.

FAQ

It goes dim when the last card reaches the right edge: there is nothing left to reveal. In a wide area there may be only one or two steps; if every card fits, both arrows are inactive.
The step is measured on the first card: with different widths, the movement won't land exactly on a card edge. Keep a common width (--ccr-card-w) and vary the content.
This carousel doesn't loop, on purpose: it stops when the last card is visible. For an endless row you need a marquee effect rather than a stepped carousel.