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.
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



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-disabledat the end of the row, with a:focus-visibleoutline. - 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 arerole="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.
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):
<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 / property | Default | Effect |
|---|---|---|
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
--ccr-card-w) and vary the content.