Prev & Next Buttons
This guide shows you how to add previous and next buttons to control carousel navigation in Embla Carousel.
Prerequisites
Button placement
If your carousel is draggable, the root node — the element passed to the EmblaCarousel initializer (e.g., .embla__viewport) — responds to pointer events. To avoid unintended drag interactions when users click them, place your navigation buttons outside the root element, like this:
import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')const emblaApi = EmblaCarousel(viewportNode, { loop: false })<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>
<!-- Place your navigation buttons here --></div>import React from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef] = useEmblaCarousel({ loop: false })
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>
{/* Place your navigation buttons here */} </div> )}<script setup>import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef] = useEmblaCarousel({ loop: false })</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>
<!-- Place your navigation buttons here --> </div></template>import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef] = useEmblaCarousel(() => ({ loop: false }))
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>
{/* Place your navigation buttons here */} </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let options = { loop: false }</script>
<div class="embla"> <div class="embla__viewport" 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>
<!-- Place your navigation buttons here --></div>Adding buttons
Adding navigation buttons is simple. Create your button elements and connect them to Embla's navigation methods — goToPrev and goToNext.
These methods let you scroll the carousel programmatically when users click the buttons, giving them an intuitive way to move between slides.
import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')const prevButtonNode = wrapperNode.querySelector('.embla__prev')const nextButtonNode = wrapperNode.querySelector('.embla__next')
const emblaApi = EmblaCarousel(viewportNode, { loop: false })
prevButtonNode.addEventListener('click', () => emblaApi.scrollToPrev(), false)nextButtonNode.addEventListener('click', () => emblaApi.scrollToNext(), false)<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>
<button class="embla__prev">Scroll to prev</button> <button class="embla__next">Scroll to next</button></div>import React from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false })
const goToPrev = () => emblaApi?.goToPrev() const goToNext = () => emblaApi?.goToNext()
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>
<button className="embla__prev" onClick={goToPrev}> Scroll to prev </button> <button className="embla__next" onClick={goToNext}> Scroll to next </button> </div> )}<script setup>import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false })
const goToPrev = () => emblaApi.value?.goToPrev()const goToNext = () => emblaApi.value?.goToNext()</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>
<button class="embla__prev" @click="goToPrev">Scroll to prev</button> <button class="embla__next" @click="goToNext">Scroll to next</button> </div></template>import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: false }))
const goToPrev = () => emblaApi()?.goToPrev() const goToNext = () => emblaApi()?.goToNext()
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>
<button class="embla__prev" onClick={goToPrev}> Scroll to prev </button> <button class="embla__next" onClick={goToNext}> Scroll to next </button> </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi let options = { loop: false }
const goToPrev = () => emblaApi?.goToPrev() const goToNext = () => emblaApi?.goToNext()
const onInit = (event) => { emblaApi = event.detail }</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>
<button class="embla__prev" on:click={goToPrev}>Scroll to prev</button> <button class="embla__next" on:click={goToNext}>Scroll to next</button></div>Buttons state
We'll enhance the user experience by disabling the navigation buttons when the carousel can't scroll any further.
For non-looping carousels, it's good UX to indicate when the navigation buttons can't be used — for example, when you're already at the first or last slide.
We'll handle this by toggling each button's state between enabled and disabled, updating them when:
- The
selectevent fires — when the selected scroll snap changes. - The
reinitevent fires — when the carousel resets.
.embla__prev:disabled,.embla__next:disabled { opacity: 0.3; cursor: not-allowed;}import EmblaCarousel from 'embla-carousel'
const wrapperNode = document.querySelector('.embla')const viewportNode = wrapperNode.querySelector('.embla__viewport')const prevButtonNode = wrapperNode.querySelector('.embla__prev')const nextButtonNode = wrapperNode.querySelector('.embla__next')
const emblaApi = EmblaCarousel(viewportNode, { loop: false })
prevButtonNode.addEventListener('click', () => emblaApi.scrollToPrev(), false)nextButtonNode.addEventListener('click', () => emblaApi.scrollToNext(), false)
const toggleButtonsDisabled = (emblaApi) => { const setButtonState = (button, enabled) => { button.toggleAttribute('disabled', !enabled) } setButtonState(prevButtonNode, emblaApi.canScrollToPrev()) setButtonState(nextButtonNode, emblaApi.canScrollToNext())}
toggleButtonsDisabled(emblaApi)emblaApi.on('select', toggleButtonsDisabled)emblaApi.on('reinit', toggleButtonsDisabled)<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>
<button class="embla__prev">Scroll to prev</button> <button class="embla__next">Scroll to next</button></div>import React, { useState, useEffect } from 'react'import useEmblaCarousel from 'embla-carousel-react'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false }) const [prevButtonDisabled, setPrevButtonDisabled] = useState(true) const [nextButtonDisabled, setNextButtonDisabled] = useState(true)
const goToPrev = () => emblaApi?.goToPrev() const goToNext = () => emblaApi?.goToNext()
const toggleButtonsDisabled = (emblaApi) => { setPrevButtonDisabled(!emblaApi.canGoToPrev()) setNextButtonDisabled(!emblaApi.canGoToNext()) }
useEffect(() => { if (!emblaApi) return
toggleButtonsDisabled(emblaApi) emblaApi.on('reinit', toggleButtonsDisabled) emblaApi.on('select', toggleButtonsDisabled) }, [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>
<button className="embla__prev" onClick={goToPrev} disabled={prevButtonDisabled} > Scroll to prev </button> <button className="embla__next" onClick={goToNext} disabled={nextButtonDisabled} > Scroll to next </button> </div> )}<script setup>import { ref, watch } from 'vue'import useEmblaCarousel from 'embla-carousel-vue'
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false })const prevButtonDisabled = ref(true)const nextButtonDisabled = ref(true)
const goToPrev = () => emblaApi.value?.goToPrev()const goToNext = () => emblaApi.value?.goToNext()
const toggleButtonsDisabled = (emblaApi) => { prevButtonDisabled.value = !emblaApi.canGoToPrev() nextButtonDisabled.value = !emblaApi.canGoToNext()}
watch( emblaApi, (api) => { if (!api) return
toggleButtonsDisabled(api) api.on('reinit', toggleButtonsDisabled) api.on('select', toggleButtonsDisabled) }, { 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>
<button class="embla__prev" @click="goToPrev" :disabled="prevButtonDisabled" > Scroll to prev </button> <button class="embla__next" @click="goToNext" :disabled="nextButtonDisabled" > Scroll to next </button> </div></template>import { createSignal, createEffect, on } from 'solid-js'import useEmblaCarousel from 'embla-carousel-solid'
export function EmblaCarousel() { const [emblaRef, emblaApi] = useEmblaCarousel(() => ({ loop: false })) const [prevButtonDisabled, setPrevButtonDisabled] = createSignal(true) const [nextButtonDisabled, setNextButtonDisabled] = createSignal(true)
const goToPrev = () => emblaApi()?.goToPrev() const goToNext = () => emblaApi()?.goToNext()
const toggleButtonsDisabled = (emblaApi) => { setPrevButtonDisabled(!emblaApi.canGoToPrev()) setNextButtonDisabled(!emblaApi.canGoToNext()) }
createEffect( on(emblaApi, (api) => { if (!api) return
toggleButtonsDisabled(api) api.on('reinit', toggleButtonsDisabled) api.on('select', toggleButtonsDisabled) }) )
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>
<button class="embla__prev" onClick={goToPrev} disabled={prevButtonDisabled()} > Scroll to prev </button> <button class="embla__next" onClick={goToNext} disabled={nextButtonDisabled()} > Scroll to next </button> </div> )}<script> import useEmblaCarousel from 'embla-carousel-svelte'
let emblaApi let prevButtonDisabled = true let nextButtonDisabled = true let options = { loop: false }
const goToPrev = () => emblaApi?.goToPrev() const goToNext = () => emblaApi?.goToNext()
const toggleButtonsDisabled = (emblaApi) => { prevButtonDisabled = !emblaApi.canGoToPrev() nextButtonDisabled = !emblaApi.canGoToNext() }
const onInit = (event) => { emblaApi = event.detail
toggleButtonsDisabled(emblaApi) emblaApi.on('reinit', toggleButtonsDisabled) emblaApi.on('select', toggleButtonsDisabled) }</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>
<button class="embla__prev" on:click={goToPrev} disabled={prevButtonDisabled}> Scroll to prev </button> <button class="embla__next" on:click={goToNext} disabled={nextButtonDisabled}> Scroll to next </button></div>Notes
- Button placement: Place the prev/next buttons outside the Embla root element (the one passed to the initializer, e.g.
.embla__viewport) to prevent accidental drag interactions. - Looping carousels: In looping carousels with enough slides to wrap around, the prev/next methods always trigger scrolling — buttons are never disabled.
- No-op behavior: On non-looping carousels, calling
goToPrevorgoToNextwhen already at the first or last slide has no effect. This is normal and ensures consistent, predictable behavior.