Infinite Loop
Endless carousel: after the last slide the track carries on to the first one in the same direction, without ever rewinding. Arrows, keyboard and swipe.
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 HTML only holds the real slides. On load, the script makes one full copy before them and one after (.inlp-clone), then sets the track on the first real slide without transition. There are thus three series side by side: copies, originals, copies.
A click on › slides the track one step (0.5 s). From the last slide, the next step lands on the copy of the first one, on the right; at the end of the transition (transitionend), the script puts the track back on the real first slide, with no animation. Both being identical, the eye sees nothing: the loop always carries on in the same direction. Same thing to the left with ‹.
Fast clicks are handled: if the track is still on a copy when the next click comes, it is first brought back to the matching original. The ← → keys and a horizontal drag of more than 50 px (Pointer Events) follow the same path. Under reduced motion there is no transitionend: the jump back happens right away.
The demo backgrounds are set by class (inlp-demo-1 to -5) so that the copies keep exactly the same look. Your photos go in as <img> inside the slides, and are copied too. The "Swipe" hint is decorative: its arrow sways three times then stops. Each .inlp-carousel has its own loop, nothing is declared on window, and a script pasted twice does not make the copies again.
Accessibility
- Copies invisible to assistive tech: each copy gets
aria-hidden="true"andinert, and loses itsidand roles; a screen reader only finds the real slides. - Structure:
role="region",aria-roledescription="carousel", slides as "slide" groups named "5 of 5"; only the visible slide stays reachable. - Announcement: after each change, an invisible
aria-live="polite"area says "slide 1 of 5"; going from the last to the first is announced as an ordinary step. - Controls: two real named buttons, white focus ring, ← and →. The "Swipe" hint is hidden from screen readers (
aria-hidden). - Reduced motion: no sliding and no swaying hint under
prefers-reduced-motion: reduce; the slide changes at once and the loop stays endless.
Browser compatibility
About 5 KB of dependency-free JavaScript. Checked on 08/11/2026 with Playwright in Chromium, Firefox and WebKit: 15 slides in the track (5 + 2 × 5 copies), all with their background, order 1-2-3-4-5-1-2-3 going forward and 2-1-5 going back, twelve random bursts of fast clicks with no drift, two independent carousels, script pasted twice without new copies, finger swipe (Chromium, 390 px) and mouse drag, reduced motion. Minimum versions from @mdn/browser-compat-data 8.1.8 (inert).
Without inert, copies stay hidden from screen readers (aria-hidden) but a link inside them could receive focus. Without aspect-ratio (Safari 14), set --inlp-height. Without JavaScript: no copies, the first slide alone.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0170-root">
<div class="inlp-carousel" role="region" aria-roledescription="carousel" aria-label="Slideshow" data-slide="slide" data-of="of">
<div class="inlp-track">
<div class="inlp-slide inlp-demo-1"><span class="inlp-caption">Slide 1</span></div>
<div class="inlp-slide inlp-demo-2"><span class="inlp-caption">Slide 2</span></div>
<!-- … only the real slides: the copies are made by the script -->
</div>
<button type="button" class="inlp-arrow inlp-prev" aria-label="Previous slide"><svg>…</svg></button>
<button type="button" class="inlp-arrow inlp-next" aria-label="Next slide"><svg>…</svg></button>
<div class="inlp-hint" aria-hidden="true"><svg>…</svg> Swipe</div>
</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 |
|---|---|---|
Slide duration (CSS — --inlp-duration on .fx-0170-root) |
0.5s | The silent jump back waits for the end of this transition. |
Width, format, height (CSS — --inlp-width, --inlp-ratio, --inlp-height) |
640px · 16 / 10 · auto | The frame follows its container up to the maximum width. |
Arrows and corners (CSS — --inlp-arrow-bg, --inlp-radius) |
white at 20% · 1rem | Button background and frame corners. |
"Swipe" hint (HTML — .inlp-hint) |
shown at the bottom, arrow sways 3 times | Remove the block on desktop or in a small component; change the word for another language. |
Demo backgrounds (CSS — .inlp-demo-1 to .inlp-demo-5) |
purple to yellow gradients | Remove them with your images; prefer classes to :nth-child, which the copies would not match. |
Demo area (CSS — --inlp-stage-bg, --inlp-stage-padding, --inlp-stage-min-height) |
#0a0a0f · 25px 16px · 300px | Set to transparent, 0, 0 in your page. |
Announced words (HTML — data-slide, data-of, aria-label) |
slide · of · Slideshow | "day 3 of 5", "city 2 of 5"… |
FAQ
id attributes are removed from the copies to avoid duplicates, and the copies are inert: a link inside them can be neither clicked nor reached by keyboard. During the split second a copy is visible, the next click brings you back to the real slide anyway.