Zazz Design Framework
Components

Carousel

A draggable, looping carousel built on Embla and wrapped in the embla-carousel web component, with dots, thumbnails, keyboard nav, and lightbox support driven entirely by markup.

<embla-carousel> is the carousel root. Put Embla options on the element, compose the viewportcontainerslide structure inside, and the script wires up dragging, looping, navigation, dots, and thumbnails from the markup. There's no JavaScript to configure.

The element uses light DOM, so the markup you write is the markup that gets styled. It initializes when connected, defers while inside a closed <dialog>, and tears down its Embla instances when removed.

Default

Loading components…

Anatomy

Mark the structural roles with data-embla. Each slide sets its own responsive width with basis-* utilities, so the same markup shows one slide on a phone and several on a wide screen.

<embla-carousel data-embla-loop="true">
  <div data-embla="viewport">
    <div data-embla="container">
      <div data-embla="slide" class="basis-full @sm:basis-1/2 @md:basis-1/3">Slide 1</div>
      <div data-embla="slide" class="basis-full @sm:basis-1/2 @md:basis-1/3">Slide 2</div>
      <div data-embla="slide" class="basis-full @sm:basis-1/2 @md:basis-1/3">Slide 3</div>
    </div>
  </div>
  <button type="button" data-embla="prev">Prev</button>
  <button type="button" data-embla="next">Next</button>
</embla-carousel>
data-emblaRole
viewportThe visible window (required)
containerThe flex track of slides
slideEach slide; set its width with basis-*
prev / nextNavigation buttons (optional)
dots / dotDot pagination container and template (optional)
thumbsLinked thumbnail carousel container (optional)

Setup

The docs preview loads the scripts for you. To use a carousel in your own page, include the Embla CDN bundles, the shared utilities, and the init script:

<script src="./zazz/scripts/utils.js"></script>
<script src="https://unpkg.com/embla-carousel/embla-carousel.umd.js"></script>
<!-- Optional plugins -->
<script src="https://unpkg.com/embla-carousel-autoplay/embla-carousel-autoplay.umd.js"></script>
<script src="https://unpkg.com/embla-carousel-auto-scroll/embla-carousel-auto-scroll.umd.js"></script>
<script src="https://unpkg.com/embla-carousel-class-names/embla-carousel-class-names.umd.js"></script>
<script src="./zazz/scripts/embla.js"></script>

Carousels auto-initialize on DOMContentLoaded.

Options

Every Embla core option goes on <embla-carousel> as a data-embla-* attribute. Names convert from kebab-case to camelCase and values are type-coerced, so data-embla-loop="true" becomes { loop: true }.

<embla-carousel data-embla-loop="true" data-embla-align="start">…</embla-carousel>
AttributeEffect
data-embla-loop"true" wraps around at the ends
data-embla-alignstart, center, or end
data-embla-keyboard"false" opts out of arrow keys

Plugins

Enable a plugin with a bare attribute and configure it with prefixed options. Each plugin needs its CDN bundle from the setup above.

<!-- autoplay, advancing every 3 seconds -->
<embla-carousel data-embla-autoplay data-embla-autoplay-delay="3000">…</embla-carousel>
PluginAttribute prefixCDN bundle
Autoplaydata-embla-autoplay-*embla-carousel-autoplay
Auto Scrolldata-embla-autoscroll-*embla-carousel-auto-scroll
Class Namesdata-embla-classnames-*embla-carousel-class-names

Dot navigation

Add a dots container with a single template dot. The script clones the template once per slide and tracks the active one with .is-active.

<div data-embla="dots">
  <button type="button" data-embla="dot"></button>
</div>

Thumbnail navigation

Nest a data-embla="thumbs" container inside the same <embla-carousel> to get a synced secondary carousel. Thumb options take the data-embla-thumbs-* prefix and default to containScroll: "keepSnaps" and dragFree: true.

<embla-carousel>
  <div data-embla="viewport">
    <div data-embla="container">
      <div data-embla="slide">Full image 1</div>
      <div data-embla="slide">Full image 2</div>
    </div>
  </div>

  <div data-embla="thumbs">
    <div data-embla="viewport">
      <div data-embla="container">
        <div data-embla="slide">Thumb 1</div>
        <div data-embla="slide">Thumb 2</div>
      </div>
    </div>
  </div>
</embla-carousel>

Keyboard navigation

Arrow keys ( / ) scroll the active carousel: one inside an open <dialog>, or the one holding the focused element. Keys are ignored when focus sits in a form field or contenteditable region. Turn it off per carousel with data-embla-keyboard="false".

Dialog and lightbox

A carousel can't measure its viewport until its <dialog> is open, so Zazz defers it: a MutationObserver watches each dialog's open attribute and initializes any carousel inside once it opens. Set data-embla-start on the trigger to open at a specific slide.

<button commandfor="gallery-dialog" data-embla-start="2">Open at slide 3</button>

The lightbox builds on this: the same Embla roles plus a fullscreen <dialog> and a thumb strip.

When slides are clickable (a lightbox trigger with commandfor, say), a drag is told apart from a click, and clicks are suppressed for a short window after a drag so a swipe never fires an accidental tap.

Re-initialization

After an SPA-style navigation or any dynamic content insertion, re-scan for new carousels:

EmblaInit.init(newContentElement);

The navigation script calls this for you after swapping <main>. init() skips carousels it has already wired (data-embla-init) and defers any inside a closed dialog.

The lower-level data-embla="root" wrapper still works for hand-built Embla markup, but prefer <embla-carousel> for components and any markup that may be inserted or removed at runtime.

On this page