Events
Embla Carousel provides a set of events you can listen to for reacting to carousel updates and user interactions.
Usage
Events are available only on an initialized carousel instance. They fire throughout the carousel's lifecycle, and any added listeners remain active even after a hard reset with the reInit method.
Adding event listeners
After initializing the carousel, you can subscribe to events. The following example shows how to listen for the slidesinview event:
import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')
const emblaApi = EmblaCarousel(viewportNode, { loop: true })
const logSlidesInView = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`)}
emblaApi.on('slidesinview', logSlidesInView)<div class="embla"> <div class="embla__viewport"> <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>import React, { useEffect } from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSlidesInView = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) }
useEffect(() => { if (!emblaApi) return emblaApi.on('slidesinview', logSlidesInView) }, [emblaApi])
return ( <div className="embla"> <div className="embla__viewport" 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> </div> )}<script setup>import { watch } from 'vue'import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSlidesInView = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`)}
watch( emblaApi, (api) => { if (!api) return api.on('slidesinview', logSlidesInView) }, { immediate: true })</script>
<template> <div class="embla"> <div class="embla__viewport" 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> </div></template>import { createEffect, on } from 'solid-js'import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: true }))
const logSlidesInView = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) }
createEffect( on(emblaApi, (api) => { if (!api) return api.on('slidesinview', logSlidesInView) }) )
return ( <div class="embla"> <div class="embla__viewport" 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> </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi let options = { loop: true }
const logSlidesInView = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) }
const onInit = (event) => { emblaApi = event.detail emblaApi.on('slidesinview', logSlidesInView) }</script>
<div class="embla"> <div class="embla__viewport" on:emblainit={onInit} use:useEmblaCarousel={{ 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></div>Note: Starting with Svelte 5, the on: event handlers have been
deprecated. However, on:emblainit will remain for backward
compatibility.
Removing event listeners
To remove an event listener, call the off method and pass the same callback reference that was originally used with on.
import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')
const emblaApi = EmblaCarousel(viewportNode, { loop: true })
const logSlidesInViewOnce = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) emblaApi.off('slidesinview', logSlidesInViewOnce)}
emblaApi.on('slidesinview', logSlidesInViewOnce)<div class="embla"> <div class="embla__viewport"> <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>import React, { useEffect } from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSlidesInViewOnce = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) emblaApi.off('slidesinview', logSlidesInViewOnce) }
useEffect(() => { if (!emblaApi) return emblaApi.on('slidesinview', logSlidesInViewOnce) }, [emblaApi])
return ( <div className="embla"> <div className="embla__viewport" 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> </div> )}<script setup>import { watch } from 'vue'import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSlidesInViewOnce = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) emblaApi.off('slidesinview', logSlidesInViewOnce)}
watch( emblaApi, (api) => { if (!api) return api.on('slidesinview', logSlidesInViewOnce) }, { immediate: true })</script>
<template> <div class="embla"> <div class="embla__viewport" 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> </div></template>import { createEffect, on } from 'solid-js'import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: true }))
const logSlidesInViewOnce = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) emblaApi.off('slidesinview', logSlidesInViewOnce) }
createEffect( on(emblaApi, (api) => { if (!api) return api.on('slidesinview', logSlidesInViewOnce) }) )
return ( <div class="embla"> <div class="embla__viewport" 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> </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi let options = { loop: true }
const logSlidesInViewOnce = (emblaApi, event) => { console.log(`${event.type}: ${event.detail.slidesInView}`) emblaApi.off('slidesinview', logSlidesInViewOnce) }
const onInit = (event) => { emblaApi = event.detail emblaApi.on('slidesinview', logSlidesInViewOnce) }</script>
<div class="embla"> <div class="embla__viewport" on:emblainit={onInit} use:useEmblaCarousel={{ 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></div>Note: Starting with Svelte 5, the on: event handlers have been
deprecated. However, on:emblainit will remain for backward
compatibility.
Cancelling events
Some events are cancellable, allowing you to prevent Embla Carousel's internal logic from running.
To cancel an event, return false from the event handler. Returning any other value—such as true or undefined—will allow the event to proceed normally.
See the event reference to check which events are cancellable.
This code prevents pointer events (dragging) on the carousel whenever the allowPointerDownEvent variable is false.
import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')
const emblaApi = EmblaCarousel(viewportNode, { loop: true })let allowPointerDownEvent = false
emblaApi.on('pointerdown', () => { return allowPointerDownEvent})<div class="embla"> <div class="embla__viewport"> <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>import { useEffect, useRef } from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }) let allowPointerDownEvent = useRef(false)
useEffect(() => { if (!emblaApi) return
emblaApi.on('pointerdown', () => { return allowPointerDownEvent.current }) }, [emblaApi])
return ( <div className="embla"> <div className="embla__viewport" 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> </div> )}<script setup>import { watch } from 'vue'import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel()let allowPointerDownEvent = false
watch( emblaApi, (api) => { if (!api) return
api.on('pointerdown', () => { return allowPointerDownEvent }) }, { immediate: true })</script>
<template> <div class="embla"> <div class="embla__viewport" 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> </div></template>import { createEffect, on } from 'solid-js'import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: true })) let allowPointerDownEvent = false
createEffect( on(emblaApi, (api) => { if (!api) return
api.on('pointerdown', () => { return allowPointerDownEvent }) }) )
return ( <div class="embla"> <div class="embla__viewport" 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> </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi let options = { loop: true } let allowPointerDownEvent = false
const onInit = (event) => { emblaApi = event.detail
emblaApi.on('pointerdown', () => { return allowPointerDownEvent }) }</script>
<div class="embla"> <div class="embla__viewport" on:emblainit={onInit} use:useEmblaCarousel={{ 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></div>Note: Starting with Svelte 5, the on: event handlers have been
deprecated. However, on:emblainit will remain for backward
compatibility.
Event data
The event handler receives the event data as the second argument, providing useful information about the event. It includes:
type— the name of the event that was triggered.detail— event-specific data. See the event reference for details on the data available for each event.
emblaApi.on('pointerdown', (api, event) => { console.log('Event name:', event.type) console.log('Event specific data:', event.detail)})TypeScript
The EmblaEventModelType is exported from the core embla-carousel package and can be used to type event callback arguments precisely:
import EmblaCarousel, { EmblaCarouselType, EmblaEventModelType} from 'embla-carousel'
const wrapperNode = <HTMLElement>document.querySelector('.embla')const viewportNode = <HTMLElement>wrapperNode.querySelector('.embla__viewport')
const emblaApi = EmblaCarousel(viewportNode, { loop: true })
const logSelectEvent = ( emblaApi: EmblaCarouselType, event: EmblaEventModelType<'select'>): void => { const { sourceSnap, targetSnap } = event.detail
console.log('Previous snap index: ', sourceSnap) console.log('Current snap index: ', targetSnap)}
emblaApi.on('select', logSelectEvent)<div class="embla"> <div class="embla__viewport"> <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>import React, { useEffect } from 'react'import { EmblaCarouselType, EmblaEventModelType } from 'embla-carousel'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSelectEvent = ( emblaApi: EmblaCarouselType, event: EmblaEventModelType<'select'> ): void => { const { sourceSnap, targetSnap } = event.detail
console.log('Previous snap index: ', sourceSnap) console.log('Current snap index: ', targetSnap) }
useEffect(() => { if (!emblaApi) return emblaApi.on('select', logSelectEvent) }, [emblaApi])
return ( <div className="embla"> <div className="embla__viewport" 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> </div> )}If you're using pnpm, you need to install embla-carousel as a
devDependency when importing types from it like demonstrated above.
This is because even though embla-carousel-react has embla-carousel as
a dependency, pnpm makes nested dependencies inaccessible by design.
<script setup lang="ts">import { watch } from 'vue'import { EmblaCarouselType, EmblaEventModelType } from 'embla-carousel'import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
const logSelectEvent = ( emblaApi: EmblaCarouselType, event: EmblaEventModelType<'select'>): void => { const { sourceSnap, targetSnap } = event.detail
console.log('Previous snap index: ', sourceSnap) console.log('Current snap index: ', targetSnap)}
watch( emblaApi, (api) => { if (!api) return api.on('select', logSelectEvent) }, { immediate: true })</script>
<template> <div class="embla"> <div class="embla__viewport" 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> </div></template>If you're using pnpm, you need to install embla-carousel as a
devDependency when importing types from it like demonstrated above.
This is because even though embla-carousel-vue has embla-carousel as a
dependency, pnpm makes nested dependencies inaccessible by design.
import { createEffect, on } from 'solid-js'import { EmblaCarouselType, EmblaEventModelType } from 'embla-carousel'import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: true }))
const logSelectEvent = ( emblaApi: EmblaCarouselType, event: EmblaEventModelType<'select'> ): void => { const { sourceSnap, targetSnap } = event.detail
console.log('Previous snap index: ', sourceSnap) console.log('Current snap index: ', targetSnap) }
createEffect( on(emblaApi, (api) => { if (!api) return api.on('select', logSelectEvent) }) )
return ( <div class="embla"> <div class="embla__viewport" 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> </div> )}If you're using pnpm, you need to install embla-carousel as a
devDependency when importing types from it like demonstrated above.
This is because even though embla-carousel-solid has embla-carousel as
a dependency, pnpm makes nested dependencies inaccessible by design.
<script lang="ts"> import { EmblaCarouselType, EmblaEventModelType, EmblaOptionsType } from 'embla-carousel' import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi: EmblaCarouselType let options: EmblaOptionsType = { loop: true }
const logSelectEvent = ( emblaApi: EmblaCarouselType, event: EmblaEventModelType<'select'> ): void => { const { sourceSnap, targetSnap } = event.detail
console.log('Previous snap index: ', sourceSnap) console.log('Current snap index: ', targetSnap) }
const onInit = (event: CustomEvent<EmblaCarouselType>): void => { emblaApi = event.detail emblaApi.on('select', logSelectEvent) }</script>
<div class="embla"> <div class="embla__viewport" on:emblainit={onInit} use:useEmblaCarousel={{ 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></div>Note: Starting with Svelte 5, the on: event handlers have been
deprecated. However, on:emblainit will remain for backward
compatibility.
If you're using pnpm, you need to install embla-carousel as a
devDependency when importing types from it like demonstrated above.
This is because even though embla-carousel-svelte has embla-carousel
as a dependency, pnpm makes nested dependencies inaccessible by design.
Alternatively, you can use the EmblaEventCallbackType to assign types to a callback constant:
import { EmblaEventCallbackType } from 'embla-carousel'
const logPointerDown: EmblaEventCallbackType<'pointerdown'> = ( emblaApi, event) => { console.log(event) // Will contain correct event type and detail}If you pass the callback directly to the on method, TypeScript will automatically infer the correct types.
emblaApi.on('pointerdown', (emblaApi, event) => { console.log(event) // Will contain correct event type and detail})Reference
Below follows an exhaustive list of all Embla Carousel events together with information about how they work.
reinit
nullnoemblaApi.on('reinit', (emblaApi) => {})Runs when the reInit method is called. When the container or any slide sizes change, or when slides are added/removed, Embla Carousel automatically calls the reInit method which will also fire this event.
destroy
nullnoemblaApi.on('destroy', (emblaApi) => {})Runs when the carousel has been destroyed using the destroy method. This only fires once and will be the last event the carousel fires.
select
{ targetSnap: number, sourceSnap: number }noemblaApi.on('select', (emblaApi, event) => { const { targetSnap, sourceSnap } = event.detail})Runs when the selected scroll snap changes. The select event is triggered by drag interactions or the goToNext, goToPrev orgoTo methods.
scroll
{ isDragging: boolean }noemblaApi.on('scroll', (emblaApi, event) => { const { isDragging } = event.detail})Runs when the carousel is scrolling. It might be a good idea to throttle this if you're doing expensive stuff in your callback function.
settle
nullnoemblaApi.on('settle', (emblaApi) => {})Runs when the carousel has settled after scroll has been triggered. Please note that this can take longer than you think when dragFree is enabled or when using slow durations.
resize
ResizeObserverEntry[]yesemblaApi.on('resize', (emblaApi, event) => { const entries = event.detail})Runs when the carousel container or the slide sizes change. It's using ResizeObserver under the hood.
slidesinview
{ slidesInView: number[], slidesLeftView: number[], slidesEnterView: number[] }noemblaApi.on('slidesinview', (emblaApi, event) => { const { slidesInView, slidesLeftView, slidesEnterView } = event.detail})Runs when any slide has entered or exited the viewport. slidesInView contains the indexes of all slides currently in view, slidesLeftView contains the indexes of slides that have just exited the viewport, and slidesEnterView contains the indexes of slides that have just entered the viewport.
slideschanged
MutationRecord[]yesemblaApi.on('slideschanged', (emblaApi, event) => { const mutationRecords = event.detail})Runs when slides are added to, or removed from the carousel container. It's using MutationObserver under the hood.
slidefocus
FocusEventyesemblaApi.on('slidefocus', (emblaApi, event) => { const focusEvent = event.detail})Runs when a slide receives focus. For example, when a focusable element like a button, link or input receives focus inside a slide.
pointerdown
TouchEvent | MouseEventyesemblaApi.on('pointerdown', (emblaApi, event) => { const pointerEvent = event.detail})Runs when the user has a pointer down on the carousel. It's triggered by a touchstart or a mousedown event.
pointermove
TouchEvent | MouseEventyesemblaApi.on('pointermove', (emblaApi, event) => { const pointerEvent = event.detail})Runs when the user is moving the pointer on the carousel. It's triggered by a touchmove or a mousemove event.
pointerup
TouchEvent | MouseEventnoemblaApi.on('pointerup', (emblaApi, event) => { const pointerEvent = event.detail})Runs when the user has released the pointer from the carousel. It's triggered by a touchend or a mouseup event.
scrolloptimize
{ slidesInView: number[], slidesLeftView: number[] }yesemblaApi.on('scrolloptimize', (emblaApi, event) => { const { slidesInView, slidesLeftView } = event.detail})Runs when the carousel applies or removes GPU acceleration to slides to optimize scrolling performance. This event is triggered with an in view offset to ensure smooth scrolling performance when slides are entering or leaving the viewport.