# Embla

> Migrate from Embla Carousel to Blossom Carousel by moving layout and behavior back to native scrolling and CSS.

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:

1. Replace the Embla viewport and container structure with a single `<BlossomCarousel>` root.
2. Move Embla options such as alignment, axis, slide sizing, and containment into CSS.
3. Replace Embla API calls with Blossom methods, native scroll APIs, and browser observers.

<note color="neutral" icon="i-lucide-lightbulb">

Most Blossom migrations replace abstract JavaScript configuration with native expressions using CSS scroll snap, grid, sticky positioning, or native scrol API's.

</note>

## Installation

Remove the Embla package for your framework and install the matching Blossom package.

<code-group sync="migration-code-style">

```bash [Vue]
npm uninstall embla-carousel-vue
npm install @blossom-carousel/vue
```

```bash [React]
npm uninstall embla-carousel-react
npm install @blossom-carousel/react
```

```bash [Svelte]
npm uninstall embla-carousel-svelte
npm install @blossom-carousel/svelte
```

```bash [Core]
npm uninstall embla-carousel
npm install @blossom-carousel/core
```

</code-group>

Import the stylesheet from your framework package once in your app.

<code-group>

```js [Vue]
import "@blossom-carousel/vue/style.css";
```

```js [React]
import "@blossom-carousel/react/style.css";
```

```js [Svelte]
import "@blossom-carousel/svelte/style.css";
```

```js [Core]
import "@blossom-carousel/core/style.css";
```

```js [Web]
import "@blossom-carousel/web/style.css";
```

</code-group>

## 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.

<code-group sync="migration-code-style">

```vue [Vue]
<script setup lang="ts">
import useEmblaCarousel from "embla-carousel-vue"; // [!code --]
import { BlossomCarousel } from "@blossom-carousel/vue"; // [!code ++]
import "@blossom-carousel/vue/style.css"; // [!code ++]
 
const [emblaRef] = useEmblaCarousel({ align: "start" }); // [!code --]
</script>

<template>
    <div class="embla" ref="emblaRef"> // [!code --]
        <div class="embla__container"> // [!code --]
        <div class="embla__slide">Slide 1</div> // [!code --]
        <div class="embla__slide">Slide 2</div> // [!code --]
        <div class="embla__slide">Slide 3</div> // [!code --]
        </div> // [!code --]
    </div> // [!code --]
    <BlossomCarousel class="carousel"> // [!code ++]
        <div class="slide snap-start">Slide 1</div> // [!code ++]
        <div class="slide snap-start">Slide 2</div> // [!code ++]
        <div class="slide snap-start">Slide 3</div> // [!code ++]
    </BlossomCarousel> // [!code ++]
</template>
```

```tsx [React]
import useEmblaCarousel from "embla-carousel-react"; // [!code --]
import { BlossomCarousel } from "@blossom-carousel/react"; // [!code ++]
import "@blossom-carousel/react/style.css"; // [!code ++]

 export function Carousel() {
  const [emblaRef] = useEmblaCarousel({ align: "start" }); // [!code --]

  return (
    <div className="embla" ref={emblaRef}> {/* [!code --] */}
      <div className="embla__container"> {/* [!code --] */}
        <div className="embla__slide">Slide 1</div> {/* [!code --] */}
        <div className="embla__slide">Slide 2</div> {/* [!code --] */}
        <div className="embla__slide">Slide 3</div> {/* [!code --] */}
      </div> {/* [!code --] */}
    </div> {/* [!code --] */}
    <BlossomCarousel className="carousel"> {/* [!code ++] */}
      <div className="slide snap-start">Slide 1</div> {/* [!code ++] */}
      <div className="slide snap-start">Slide 2</div> {/* [!code ++] */}
      <div className="slide snap-start">Slide 3</div> {/* [!code ++] */}
    </BlossomCarousel> {/* [!code ++] */}
  );
 }
```

```svelte [Svelte]
<script>
  import emblaCarouselSvelte from "embla-carousel-svelte"; // [!code --]
  import { BlossomCarousel } from "@blossom-carousel/svelte"; // [!code ++]
  import "@blossom-carousel/svelte/style.css"; // [!code ++]

  const options = { align: "start" }; // [!code --]
</script>

<div class="embla" use:emblaCarouselSvelte={{ options }}> <!-- [!code --] -->
  <div class="embla__container"> <!-- [!code --] -->
    <div class="embla__slide">Slide 1</div> <!-- [!code --] -->
    <div class="embla__slide">Slide 2</div> <!-- [!code --] -->
    <div class="embla__slide">Slide 3</div> <!-- [!code --] -->
  </div> <!-- [!code --] -->
</div> <!-- [!code --] -->
<BlossomCarousel class="carousel"> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 1</div> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 2</div> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 3</div> <!-- [!code ++] -->
</BlossomCarousel> <!-- [!code ++] -->
```

```html [Core]
<div class="embla"> <!-- [!code --] -->
  <div class="embla__container"> <!-- [!code --] -->
    <div class="embla__slide">Slide 1</div> <!-- [!code --] -->
    <div class="embla__slide">Slide 2</div> <!-- [!code --] -->
    <div class="embla__slide">Slide 3</div> <!-- [!code --] -->
  </div> <!-- [!code --] -->
</div> <!-- [!code --] -->
<div class="carousel"> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 1</div> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 2</div> <!-- [!code ++] -->
  <div class="slide snap-start">Slide 3</div> <!-- [!code ++] -->
</div> <!-- [!code ++] -->

<script type="module">
  import EmblaCarousel from "embla-carousel"; // [!code --]
  import { Blossom } from "@blossom-carousel/core"; // [!code ++]
  import "@blossom-carousel/core/style.css"; // [!code ++]

  const emblaElement = document.querySelector(".embla"); // [!code --]
  const emblaApi = EmblaCarousel(emblaElement, { align: "start" }); // [!code --]

  const carouselElement = document.querySelector(".carousel"); // [!code ++]
  const blossom = Blossom(carouselElement); // [!code ++]
  blossom.init(); // [!code ++]
</script>
```

</code-group>

## 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.

<table>
<thead>
  <tr>
    <th>
      Embla concept
    </th>
    
    <th>
      Blossom approach
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        axis: "x"
      </code>
    </td>
    
    <td>
      Automatically handled by Blossom based on native overscroll.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        align: "start"
      </code>
    </td>
    
    <td>
      Use CSS <code>
        scroll-snap-align: start
      </code>
      
       on slides.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        align: "center"
      </code>
    </td>
    
    <td>
      Use CSS <code>
        scroll-snap-align: center
      </code>
      
       on slides.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        containScroll
      </code>
    </td>
    
    <td>
      Use native scroll bounds, padding, and <code>
        scroll-padding
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        slidesToScroll
      </code>
    </td>
    
    <td>
      Use CSS <code>
        scroll-snap-align
      </code>
      
       within <code>
        :nth-child(xn)
      </code>
      
       or nest slides in groups.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dragFree
      </code>
    </td>
    
    <td>
      Omit <code>
        scroll-snap-align
      </code>
      
       or use <code>
        scroll-snap-type: proximity
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        direction: "rtl"
      </code>
    </td>
    
    <td>
      Use <code>
        dir="rtl"
      </code>
      
       or CSS <code>
        direction: rtl
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        breakpoints
      </code>
    </td>
    
    <td>
      Use CSS media queries and container queries.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loop
      </code>
    </td>
    
    <td>
      Prefer bounded native scrolling, or use the experimental repeat pattern when an infinite effect is required. See <a href="/docs/examples/experimental/repeat">
        Repeat example
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        draggable
      </code>
    </td>
    
    <td>
      Use CSS <code>
        overflow: hidden
      </code>
      
       on the carousel to disable dragging and scrolling.
    </td>
  </tr>
</tbody>
</table>

For example, this Embla setup:

```js [Embla]
useEmblaCarousel({
  axis: "x",
  align: "center",
  containScroll: "trimSnaps",
  dragFree: false,
  breakpoints: {
    "(min-width: 768px)": { align: "start" },
  },
});
```

becomes CSS:

```css [Blossom]
.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 controls call methods on the Embla API. Blossom provides `<BlossomPrev>`, `<BlossomNext>`, and `<BlossomDots>` components that link to the carousel through matching `id` and `for` props.

```ts
emblaApi.scrollPrev(); // [!code --]
emblaApi.scrollNext(); // [!code --]
```

```html
<BlossomCarousel id="gallery" class="carousel">
  <div data-blossom-slide class="slide">Slide 1</div>
  ...
</BlossomCarousel>

<div class="controls">
  <BlossomPrev for="gallery" />
  <BlossomNext for="gallery" />
</div>

<BlossomDots for="gallery" />
```

For direct slide navigation, call `goTo(index)` on the carousel. It scrolls to the slide at that zero-based index, counting slides marked with `data-blossom-slide`. Reach the carousel through a template ref in Vue, a `ref` in React, `bind:this` in Svelte, or the `<blossom-carousel>` element itself.

```ts
carousel.goTo(index);
carousel.goTo(index, { behavior: "instant" });
```

## API

Embla commonly uses `select` events and `selectedScrollSnap()` to track the active slide.

With Blossom, read the active slide from `useBlossomNavigation` in Vue, React, and Svelte. Pass the carousel `id`; it returns `activeIndex`, `count`, `canPrev`, and `canNext`, and updates during drag and free scrolling too.

```ts
emblaApi.on("select", () => { // [!code --]
  setSelectedIndex(emblaApi.selectedScrollSnap()); // [!code --]
}); // [!code --]

const nav = useBlossomNavigation("gallery"); // [!code ++]
```

In React and Svelte, read `nav.activeIndex`. In Vue, `nav` is a ref, so read `nav.value.activeIndex` in script or `nav.activeIndex` in the template. Don't destructure the result in Svelte; that loses reactivity.

With Web Components, listen for the native `scrollsnapchange` event where it's supported:

```ts
const slides = Array.from(carousel.querySelectorAll("[data-blossom-slide]"));

carousel.addEventListener("scrollsnapchange", (event) => {
  setSelectedIndex(slides.indexOf(event.snapTargetInline));
});
```

## Plugins

Embla plugins usually become either small Blossom wrappers or native platform features.

<table>
<thead>
  <tr>
    <th>
      Embla plugin pattern
    </th>
    
    <th>
      Blossom replacement
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Autoplay
    </td>
    
    <td>
      Use manual timer that scrolls the next slide into view.
    </td>
  </tr>
  
  <tr>
    <td>
      Fade
    </td>
    
    <td>
      Use CSS scroll-driven animations or intersection state.
    </td>
  </tr>
  
  <tr>
    <td>
      Auto height
    </td>
    
    <td>
      Let content define height, or update layout with <code>
        ResizeObserver
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      Class names
    </td>
    
    <td>
      Use <code>
        IntersectionObserver
      </code>
      
      , <code>
        scrollsnapchange
      </code>
      
      , or scroll-state queries.
    </td>
  </tr>
  
  <tr>
    <td>
      Wheel gestures
    </td>
    
    <td>
      Use the browser's native scroll behavior where possible.
    </td>
  </tr>
</tbody>
</table>

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.

<note color="neutral" icon="i-lucide-lightbulb">

Blossom initializes automatically when using framework components.

</note>

```ts
const [emblaRef, emblaApi] = useEmblaCarousel(); // [!code --]
emblaApi.reInit(); // [!code --]
emblaApi.destroy(); // [!code --]
const blossom = Blossom(carouselElement); // [!code ++]
blossom.init(); // [!code ++]
blossom.destroy(); // [!code ++]
```

## Migration checklist

- Remove Embla packages and install the matching Blossom package.
- Import the stylesheet from your framework package once (for example `@blossom-carousel/vue/style.css`).
- 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()` and `scrollNext()` with `<BlossomPrev>` and `<BlossomNext>`.
- Replace `scrollTo()` with `goTo(index)` on the carousel.
- Replace `select` listeners with `useBlossomNavigation` (or `scrollsnapchange` for Web Components).
- Rebuild only the Embla plugins your carousel still needs.

## Next steps

- [Installation](/docs/getting-started/installation) - Pick the package for your framework.
- [Snapping](/docs/examples/basic/snap) - Configure scroll snap behavior with CSS.
- [Buttons](/docs/examples/basic/buttons) - Add previous and next controls.
- [Dots](/docs/examples/basic/dots) - Add dot markers with `<BlossomDots>`.
- [Repeat](/docs/examples/experimental/repeat) - Explore an experimental repeating carousel pattern.
