Skip to content

Embla libraries comparison

How @rozie-ui/embla compares to the existing per-framework Embla Carousel wrappers. Embla is the de-facto dependency-free, library-agnostic carousel engine: its core is pure vanilla JS that attaches to a viewport, reads the consumer's slide DOM, and drives transform: translate3d(...). Every framework wrapper exists only to glue reactive state to that imperative engine, surface its options as props, and forward the event set. The result is a lopsided ecosystem: four official wrappers with four divergent APIs, a single-maintainer Angular community package, and nothing at all for Lit / web components. Rozie ships the same <Carousel>, with the same props, events, two-way selectedIndex, and imperative handle, to all six frameworks as pre-compiled per-framework packages.

Research snapshot: 2026-08-10. Versions and the wrapper landscape move; treat them as of that date. All six Rozie packages wrap Embla v8 (embla-carousel@^8.6); Embla v9 is RC-only and renames the API surface, so it is deliberately not targeted yet.

The wrappers at a glance

FrameworkEmbla wrapperShapeDepthNotes
Reactembla-carousel-reacta hook (useEmblaCarousel)deepOfficial, maintained. Returns a [ref, api] tuple; you wire the ref onto the viewport and call the api yourself.
Vueembla-carousel-vuea composabledeepOfficial. Returns a [ref, api] composable; Vue-idiomatic but a different surface from the React hook.
Svelteembla-carousel-sveltean action (use:emblaCarousel)deepOfficial. A Svelte action directive; again a different shape from hook/composable.
Solidembla-carousel-solida Solid primitivemoderateOfficial. A createEmblaCarousel primitive; a fourth divergent API.
Angularembla-carousel-angular (community)a directiveshallowA single-maintainer community package, version-pinned to Angular majors; not first-party.
Lit / web components(none)No wrapper exists. You hand-roll the engine attach + lifecycle yourself.
Rozie@rozie-ui/embla-*a componentdeepSame API on all six: props, events, two-way selectedIndex, handle.

On its home framework each official wrapper is a solid pick. The case for Rozie is consistency and coverage: the four official surfaces are a hook, a composable, an action, and a primitive, four different mental models for the same engine; Angular has only a community package; and Lit has nothing. A team shipping a cross-framework design system today maintains four divergent bindings plus a hand-rolled Angular/Lit story. Rozie gives them all the same <Carousel> component: the same props, events, two-way binding, and imperative handle.

Feature matrix

Cell legend: = documented out-of-the-box · = not supported / not present · ⚠️ = partial / consumer-glue-required.

Capability…-react…-vue…-svelte…-solidAngular (community)Lit (none)@rozie-ui/embla
Embla v8 engine
Idiomatic component surface⚠️ hook⚠️ composable⚠️ action⚠️ primitive⚠️ directivehand-roll<Carousel>
Option surface as props⚠️ via options object⚠️ via options object⚠️ via options object⚠️ via options object⚠️✅ 20 props
Two-way snap index⚠️ via select listener⚠️ via select listener⚠️ via select listener⚠️ via select listener⚠️selectedIndex model (echo-guarded)
Runtime option reconcile⚠️ manual reInit⚠️ manual reInit⚠️ manual reInit⚠️ manual reInit⚠️✅ 13 of 20 props $watchreInit (17 runtime-reactive in all)
Autoplay plugin toggle⚠️ wire the plugin yourself⚠️⚠️⚠️⚠️autoplay prop
Imperative handle✅ via the api object✅ via the api object✅ via the api object✅ via the api accessor⚠️hand-roll✅ uniform 14-verb $expose
Plugin / options escape hatch⚠️:plugins + :options
Config-array and declarative slides❌ (slot only):slides array OR default slot, on all six targets¹
TypeScript⚠️
Accessibility (carousel ARIA role, keyboard nav, labels)❓ not verified❓ not verified❓ not verified❓ not verified❓ not verified⚠️ arrow/dot aria-labels only — no carousel role, no keyboard nav on the viewport; fuller carousel a11y is on the roadmap
Zero-config styling, re-skinnable⚠️ unstyled, wire it⚠️ unstyled, wire it⚠️ unstyled, wire it⚠️ unstyled, wire it⚠️ unstyled, wire it✅ CSS-var tokens + shadcn/Material/Bootstrap bridges
Same API on all 6 frameworks

¹ Declarative (default-slot) mode resolves on Lit via the $slotted.<name> sigil, which reaches across the shadow boundary that a bare container.querySelectorAll('.rozie-embla__slide') cannot see through.

Where Rozie wins today

  • First-class packages everywhere — including Lit, which has no Embla wrapper at all, and Angular, which has only a single-maintainer community package. Lit gets an Embla custom element; Angular gets a signals component from the same family as the other five.
  • The same component surface everywhere. The four official wrappers are a hook, a composable, an action, and a primitive. @rozie-ui/embla is one <Carousel> component with the same props, events, and handle: one component to learn, document, and migrate across your stack.
  • A real two-way snap index. The selectedIndex model reads and drives the carousel: dragging echoes the new index out (via Embla's select event), and a consumer write scrolls the carousel (echo-guarded so a programmatic scrollTo doesn't ping-pong). The official wrappers surface the index via a one-way select listener; you wire the write-back yourself.
  • A uniform 14-verb imperative handle (scrollNext / scrollPrev / scrollToIndex / reInitCarousel / canScrollNext / canScrollPrev / getSelectedIndex / scrollSnapList / scrollProgress / slidesInView / slidesNotInView / previousScrollSnap / getPlugins / getInstance) grabbed with each framework's native ref, identical on every target.
  • Two slide-source modes from one component. Pass a :slides config array, or drop declarative .rozie-embla__slide children into the default slot. The official wrappers only support consumer-authored slide DOM.
  • getInstance() is always one hop from the raw engine, so the full Embla v8 API is reachable on any target when the curated surface doesn't cover something.
  • Zero-config styling that re-skins to any design system. Every rendered value is a --rozie-embla-* CSS custom property with a built-in fallback, plus ready-made token bridges for shadcn/ui, Material 3, and Bootstrap 5. The official wrappers ship the bare engine unstyled and you wire arrows, dots, and thumbnails yourself; Rozie works on drop-in yet stays fully re-skinnable.

What Rozie defers

  • React/Vue/Svelte/Solid depth on their home framework. The four official wrappers are mature, maintained, and shaped to their framework's own conventions (a React dev expects a hook; a Svelte dev expects an action). On its home framework each is a perfectly good pick. Rozie's value is the same component everywhere, with Lit and Angular getting an Embla component they otherwise lack. For anything outside the curated surface, getInstance() hands you the raw engine on every target.
  • Per-option setters. Embla itself ships no per-option setters; reInit(options) is the only update path. So @rozie-ui/embla $watches each runtime option and calls reInit, exactly as you would by hand with the official wrappers. A consumer rapidly mutating options can thrash reInit; debounce on the consumer side if needed (Embla's native watchSlides is incremental for slide changes).
  • @rozie-ui/embla is pre-1.0 and younger than the official wrappers. The full surface is documented in the showcase & API.
  • Embla v8, not v9. These packages wrap the mature v8. Embla v9 (RC at time of writing) renames the API surface; teams that want v9 are better served by it directly once it ships stable.

Try it

The @rozie-ui/embla showcase + API reference documents the @rozie-ui/embla-* packages: one pre-compiled, per-framework install (npm i @rozie-ui/embla-react embla-carousel embla-carousel-autoplay, etc.). There is no engine CSS to import: Embla's carousel skeleton ships scoped and fully-tokenised inside the component, with optional one-line theme bridges for shadcn/ui, Material 3, and Bootstrap 5. The showcase walks the two-way selectedIndex binding, the event surface, the imperative handle, theming, and the prev/next/dots recipe.

Cross-references

Pre-1.0 — APIs may change between minor versions.