Embla
Embla creates a carousel engine around a viewport, container, options object, event system, and plugin API.
Blossom keeps the carousel as a native scroll container. Layout, snapping, alignment, spacing, direction, and responsive behavior belong in CSS.
Migration mindset
When moving from Embla to Blossom, migrate the carousel in three steps:
- Replace the Embla viewport and container structure with a single
<BlossomCarousel>root. - Move Embla options such as alignment, axis, slide sizing, and containment into CSS.
- Replace Embla API calls with Blossom methods, native scroll APIs, and browser observers.
Installation
Remove the Embla package for your framework and install the matching Blossom package.
npm uninstall embla-carousel-vue
npm install @blossom-carousel/vue
npm uninstall embla-carousel-react
npm install @blossom-carousel/react
npm uninstall embla-carousel-svelte
npm install @blossom-carousel/svelte
npm uninstall embla-carousel
npm install @blossom-carousel/core
Import Blossom's core stylesheet once in your app.
import "@blossom-carousel/core/style.css";
Component structure
Embla usually needs a viewport element, a container element, and slide elements. With Blossom, the carousel root is the scroll container and the slides are direct children.
<script setup lang="ts">
import useEmblaCarousel from "embla-carousel-vue";import { BlossomCarousel } from "@blossom-carousel/vue";import "@blossom-carousel/core/style.css";
const [emblaRef] = useEmblaCarousel({ align: "start" });</script>
<template>
<div class="embla" ref="emblaRef"> <div class="embla__container"> <div class="embla__slide">Slide 1</div> <div class="embla__slide">Slide 2</div> <div class="embla__slide">Slide 3</div> </div> </div> <BlossomCarousel class="carousel"> <div class="slide snap-start">Slide 1</div> <div class="slide snap-start">Slide 2</div> <div class="slide snap-start">Slide 3</div> </BlossomCarousel></template>
import useEmblaCarousel from "embla-carousel-react";import { BlossomCarousel } from "@blossom-carousel/react";import "@blossom-carousel/core/style.css";
export function Carousel() {
const [emblaRef] = useEmblaCarousel({ align: "start" });
return (
<div className="embla" ref={emblaRef}> <div className="embla__container"> <div className="embla__slide">Slide 1</div> <div className="embla__slide">Slide 2</div> <div className="embla__slide">Slide 3</div> </div> </div> <BlossomCarousel className="carousel"> <div className="slide snap-start">Slide 1</div> <div className="slide snap-start">Slide 2</div> <div className="slide snap-start">Slide 3</div> </BlossomCarousel> );
}
<script>
import emblaCarouselSvelte from "embla-carousel-svelte"; import BlossomCarousel from "@blossom-carousel/svelte"; import "@blossom-carousel/core/style.css";
const options = { align: "start" };</script>
<div class="embla" use:emblaCarouselSvelte={{ options }}> <div class="embla__container"> <div class="embla__slide">Slide 1</div> <div class="embla__slide">Slide 2</div> <div class="embla__slide">Slide 3</div> </div></div><BlossomCarousel class="carousel"> <div class="slide snap-start">Slide 1</div> <div class="slide snap-start">Slide 2</div> <div class="slide snap-start">Slide 3</div></BlossomCarousel><div class="embla"> <div class="embla__container"> <div class="embla__slide">Slide 1</div> <div class="embla__slide">Slide 2</div> <div class="embla__slide">Slide 3</div> </div></div><div class="carousel"> <div class="slide snap-start">Slide 1</div> <div class="slide snap-start">Slide 2</div> <div class="slide snap-start">Slide 3</div></div>
<script type="module">
import EmblaCarousel from "embla-carousel"; import { Blossom } from "@blossom-carousel/core"; import "@blossom-carousel/core/style.css";
const emblaElement = document.querySelector(".embla"); const emblaApi = EmblaCarousel(emblaElement, { align: "start" });
const carouselElement = document.querySelector(".carousel"); const blossom = Blossom(carouselElement); blossom.init();</script>
Options
Embla options describe how the carousel engine should calculate slide positions. In Blossom, those same decisions are usually regular CSS on the scroll container and slides.
| Embla concept | Blossom approach |
|---|---|
axis: "x" | Automatically handled by Blossom based on native overscroll. |
align: "start" | Use CSS scroll-snap-align: start on slides. |
align: "center" | Use CSS scroll-snap-align: center on slides. |
containScroll | Use native scroll bounds, padding, and scroll-padding. |
slidesToScroll | Use CSS scroll-snap-align within :nth-child(xn) or nest slides in groups. |
dragFree | Omit scroll-snap-align or use scroll-snap-type: proximity. |
direction: "rtl" | Use dir="rtl" or CSS direction: rtl. |
breakpoints | Use CSS media queries and container queries. |
loop | Prefer bounded native scrolling, or use the experimental repeat pattern when an infinite effect is required. See Repeat example. |
draggable | Use CSS overflow: hidden on the carousel to disable dragging and scrolling. |
For example, this Embla setup:
useEmblaCarousel({
axis: "x",
align: "center",
containScroll: "trimSnaps",
dragFree: false,
breakpoints: {
"(min-width: 768px)": { align: "start" },
},
});
becomes CSS:
.carousel {
scroll-snap-type: inline mandatory;
scroll-padding-inline: 1rem;
}
.slide {
scroll-snap-align: center;
}
@media (min-width: 768px) {
.slide {
scroll-snap-align: start;
}
}
Controls
Embla and Blossom both expose carousel controls through their respective APIs.
const [emblaRef, emblaApi] = useEmblaCarousel();emblaApi.scrollPrev();emblaApi.scrollNext();const blossom = ref(null);blossom.value.prev();blossom.value.next();For direct slide navigation, use native scrolling on the target slide.
slides[index].scrollIntoView({
behavior: "smooth",
inline: "center",
block: "nearest",
});
API
Embla commonly uses select events and selectedScrollSnap() to track the active slide.
With Blossom, the carousel is a real scroll container, so use browser APIs such as IntersectionObserver, scrollsnapchange, or scroll events depending on the browser support you need.
emblaApi.on("select", () => { setSelectedIndex(emblaApi.selectedScrollSnap());});
carousel.addEventListener("scrollsnapchange", (event) => { setSelectedIndex(event.detail.index);});
Or use IntersectionObserver for wider browser support
const slides = Array.from(carousel.children);const observer = new IntersectionObserver( (entries) => { const activeEntry = entries.find((entry) => entry.isIntersecting); if (activeEntry) { const index = slides.indexOf(activeEntry.target); setSelectedIndex(index); } }, { root: carousel, threshold: 0.5 },);slides.forEach((slide) => observer.observe(slide));Plugins
Embla plugins usually become either small Blossom wrappers or native platform features.
| Embla plugin pattern | Blossom replacement |
|---|---|
| Autoplay | Use a timer that calls next() or scrolls the next slide into view. |
| Fade | Use CSS scroll-driven animations or intersection state. |
| Auto height | Let content define height, or update layout with ResizeObserver. |
| Class names | Use IntersectionObserver, scrollsnapchange, or scroll-state queries. |
| Wheel gestures | Use the browser's native scroll behavior where possible. |
Keep plugin migrations small. Start from a working Blossom carousel, then add the behavior back only if the product still needs it.
Lifecycle
Embla's and Blossom's lifecycle methods are similar.
const [emblaRef, emblaApi] = useEmblaCarousel();emblaApi.reInit();emblaApi.destroy();const blossom = Blossom(carouselElement);blossom.init();blossom.destroy();Migration checklist
- Remove Embla packages and install the matching Blossom package.
- Import
@blossom-carousel/core/style.cssonce. - Replace Embla viewport and container markup with
<BlossomCarousel>and direct slide children. - Move slide sizing, alignment, gaps, snapping, direction, and breakpoints into CSS.
- Replace
scrollPrev()andscrollNext()withprev()andnext(). - Replace
scrollTo()withscrollIntoView()or nativescrollTo()on the carousel element. - Replace
selectlisteners with browser observers or scroll snap events. - Rebuild only the Embla plugins your carousel still needs.
Next steps
- Installation - Pick the package for your framework.
- Snapping - Configure scroll snap behavior with CSS.
- Buttons - Add previous and next controls.
- Dots - Track the active slide with browser APIs.
- Repeat - Explore an experimental repeating carousel pattern.