Skip to content

Carousel

CSS + JavaScriptembla-carousel.js

A carousel built on Embla. embla-carousel.js reads data-embla* attributes and wires the slider — drag, loop, autoplay, autoscroll, prev/next buttons, pagination, and a current/total counter. Embla itself loads from a CDN, so there’s nothing else to install.

Carousel
Open ↗
components/carousel.html
<div class="embla [--slide-size:85%] sm:[--slide-size:55%]" data-embla="center" data-embla-loop data-embla-drag>
<div class="embla-viewport" data-embla-viewport>
<div class="embla-container">
<div class="embla-slide" data-embla-slide>
<div
class="flex h-40 items-center justify-center rounded-2xl bg-background-primary text-4xl font-bold text-foreground-primary"
>
1
</div>
</div>
<div class="embla-slide" data-embla-slide>
<div
class="flex h-40 items-center justify-center rounded-2xl bg-background-positive text-4xl font-bold text-foreground-positive"
>
2
</div>
</div>
<div class="embla-slide" data-embla-slide>
<div
class="flex h-40 items-center justify-center rounded-2xl bg-background-warning text-4xl font-bold text-foreground-warning"
>
3
</div>
</div>
<div class="embla-slide" data-embla-slide>
<div
class="flex h-40 items-center justify-center rounded-2xl bg-background-negative text-4xl font-bold text-foreground-negative"
>
4
</div>
</div>
</div>
</div>
<div class="mt-4 flex items-center justify-between gap-4">
<div class="flex gap-2">
<button type="button" class="button-secondary p-2" data-embla-prev aria-label="Previous">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M15 6l-6 6l6 6" />
</svg>
</button>
<button type="button" class="button-secondary p-2" data-embla-next aria-label="Next">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M9 6l6 6l-6 6" />
</svg>
</button>
</div>
<div
class="flex items-center gap-2"
data-embla-pagination="size-2.5 rounded-full bg-border transition-colors embla-pagination-is-selected:bg-foreground-primary cursor-pointer"
></div>
<p class="text-sm text-foreground-subtle"><span data-embla-current>1</span> / <span data-embla-total>4</span></p>
</div>
</div>

Three nesting levels — root, viewport, container — then slides:

<div class="embla [--slide-size:85%]" data-embla="center" data-embla-loop data-embla-drag>
<div class="embla-viewport" data-embla-viewport>
<div class="embla-container">
<div class="embla-slide" data-embla-slide></div>
<div class="embla-slide" data-embla-slide></div>
</div>
</div>
<button data-embla-prev></button>
<button data-embla-next></button>
<div data-embla-pagination="<dot classes>"></div>
<span data-embla-current>1</span> / <span data-embla-total>2</span>
</div>

Slides-per-view is controlled by the --slide-size variable on the root (e.g. [--slide-size:55%]), and the gap by --gap.

Attribute Effect
data-embla="start|center|end" Slide alignment.
data-embla-loop Infinite loop.
data-embla-drag / data-embla-drag-free Pointer dragging (free = momentum).
data-embla-vertical Vertical axis.
data-embla-duration Transition duration.
data-embla-autoplay="<ms>" Auto-advance.
data-embla-autoscroll="<speed>" + data-embla-autoscroll-direction Continuous scroll.
Attribute Role
data-embla-viewport The clipping viewport (required).
data-embla-slide A slide.
data-embla-prev / data-embla-next Navigation buttons.
data-embla-pagination="<classes>" Container; dots are generated with those classes (active dot gets is-selected).
data-embla-current / data-embla-total Live index / count.
data-embla-created [auto-set] [internal] processing guard.

Style states with the variants: embla-is-snapped:, embla-is-in-view:, embla-is-dragging:, embla-pagination-is-selected:.