Carousel

A carousel with motion and swipe built using Embla.

Usage

Items

Use the items prop as an array and render each item using the default slot:

Use your mouse to drag the carousel horizontally on desktop.
<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

You can control how many items are visible by using the basis / width utility classes on the item:

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-1/3' }">
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>

Orientation

Use the orientation prop to change the orientation of the Progress. Defaults to horizontal.

Use your mouse to drag the carousel vertically on desktop.
<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    orientation="vertical"
    :items="items"
    class="w-full max-w-xs mx-auto"
    :ui="{ container: 'h-[336px]' }"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
You need to specify a height on the container in vertical orientation.

Arrows

Use the arrows prop to display prev and next buttons.

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" arrows :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Prev / Next

Use the prev and next props to customize the prev and next buttons with any Button props.

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    arrows
    :prev="{ color: 'primary' }"
    :next="{ variant: 'solid' }"
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Prev / Next Icons

Use the prev-icon and next-icon props to customize the buttons Icon. Defaults to i-lucide-arrow-left / i-lucide-arrow-right.

<script setup lang="ts">
defineProps<{
  prevIcon?: string
  nextIcon?: string
}>()

const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    arrows
    :prev-icon="prevIcon"
    :next-icon="nextIcon"
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
You can customize these icons globally in your app.config.ts under ui.icons.arrowLeft / ui.icons.arrowRight key.

Dots

Use the dots prop to display a list of dots to scroll to a specific slide.

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" dots :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

The number of dots is based on the number of slides displayed in the view:

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" dots :items="items" :ui="{ item: 'basis-1/3' }">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Plugins

The Carousel component implements the official Embla Carousel plugins.

Autoplay

This plugin is used to extend Embla Carousel with autoplay functionality.

Use the autoplay prop as a boolean or an object to configure the Autoplay plugin.

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    arrows
    dots
    :autoplay="{ delay: 2000 }"
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>
In this example, we're using the loop prop for an infinite carousel.

Auto Scroll

This plugin is used to extend Embla Carousel with auto scroll functionality.

Use the auto-scroll prop as a boolean or an object to configure the Auto Scroll plugin.

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    dots
    arrows
    auto-scroll
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>
In this example, we're using the loop prop for an infinite carousel.

Auto Height

This plugin is used to extend Embla Carousel with auto height functionality. It changes the height of the carousel container to fit the height of the highest slide in view.

Use the auto-height prop as a boolean or an object to configure the Auto Height plugin.

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/320?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/320?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/320?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    auto-height
    arrows
    dots
    :items="items"
    :ui="{
      container: 'transition-[height]',
      controls: 'absolute -top-8 inset-x-12',
      dots: '-top-7',
      dot: 'w-6 h-1'
    }"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
In this example, we add the transition-[height] class on the container to animate the height change.

Class Names

Class Names is a class name toggle utility plugin for Embla Carousel that enables you to automate the toggling of class names on your carousel.

Use the class-names prop as a boolean or an object to configure the Class Names plugin.

<script setup lang="ts">
const items = [
  'https://picsum.photos/528/528?random=1',
  'https://picsum.photos/528/528?random=2',
  'https://picsum.photos/528/528?random=3',
  'https://picsum.photos/528/528?random=4',
  'https://picsum.photos/528/528?random=5',
  'https://picsum.photos/528/528?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    class-names
    arrows
    :items="items"
    :ui="{
      item: 'basis-[70%] transition-opacity [&:not(.is-snapped)]:opacity-10'
    }"
    class="mx-auto max-w-sm"
  >
    <img :src="item" width="264" height="264" class="rounded-lg">
  </UCarousel>
</template>
In this example, we add the transition-opacity [&:not(.is-snapped)]:opacity-10 classes on the item to animate the opacity change.

Fade

This plugin is used to replace the Embla Carousel scroll functionality with fade transitions.

Use the fade prop as a boolean or an object to configure the Fade plugin.

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    fade
    arrows
    dots
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Wheel Gestures

This plugin is used to extend Embla Carousel with the ability to use the mouse/trackpad wheel to navigate the carousel.

Use the wheel-gestures prop as a boolean or an object to configure the Wheel Gestures plugin.

Use your mouse wheel to scroll the carousel.
<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    wheel-gestures
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>

API

Props

Prop Default Type
prev

{ size: 'md', color: 'neutral', variant: 'link' }

ButtonProps

Configure the prev button when arrows are enabled.

prevIcon

appConfig.ui.icons.arrowLeft

string

The icon displayed in the prev button.

next

{ size: 'md', color: 'neutral', variant: 'link' }

ButtonProps

Configure the next button when arrows are enabled.

nextIcon

appConfig.ui.icons.arrowRight

string

The icon displayed in the next button.

arrows

false

boolean

Display prev and next buttons to scroll the carousel.

dots

false

boolean

Display dots to scroll to a specific slide.

orientation

"horizontal"

"vertical" | "horizontal"

items

AcceptableValue[]

autoplay

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Autoplay plugin

autoScroll

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Auto Scroll plugin

autoHeight

false

boolean | Partial<CreateOptionsType<{ active: boolean; breakpoints: { [key: string]: Omit<Partial<...>, "breakpoints">; }; }>>

Enable Auto Height plugin

classNames

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Class Names plugin

fade

false

boolean | Partial<CreateOptionsType<{ active: boolean; breakpoints: { [key: string]: Omit<Partial<...>, "breakpoints">; }; }>>

Enable Fade plugin

wheelGestures

false

any

Enable Wheel Gestures plugin

active

true

boolean

duration

25

number

align

"center"

"start" | "center" | "end" | (viewSize: number, snapSize: number, index: number): number

containScroll

"trimSnaps"

false | "trimSnaps" | "keepSnaps"

slidesToScroll

1

number | "auto"

dragFree

false

boolean

dragThreshold

10

number

inViewThreshold

0

number | number[]

loop

false

boolean

skipSnaps

false

boolean

startIndex

0

number

watchDrag

true

false | true | (emblaApi: EmblaCarouselType, evt: PointerEventType): boolean | void

watchResize

true

false | true | (emblaApi: EmblaCarouselType, entries: ResizeObserverEntry[]): boolean | void

watchSlides

true

false | true | (emblaApi: EmblaCarouselType, mutations: MutationRecord[]): boolean | void

watchFocus

true

false | true | (emblaApi: EmblaCarouselType, evt: FocusEvent): boolean | void

breakpoints

{}

{ [key: string]: Omit<Partial<CreateOptionsType<{ align: AlignmentOptionType; axis: AxisOptionType; container: string | HTMLElement | null; slides: string | HTMLElement[] | NodeListOf<...> | null; ... 13 more ...; watchFocus: FocusHandlerOptionType; }>>, "breakpoints">; }

ui

PartialString<{ root: string; viewport: string; container: string; item: string; controls: string; arrows: string; prev: string; next: string; dots: string; dot: string[]; }>

Slots

Slot Type
default

{ item: AcceptableValue; index: number; }

Expose

You can access the typed component instance using useTemplateRef.

<script setup lang="ts">
const carousel = useTemplateRef('carousel')
</script>

<template>
  <UCarousel ref="carousel" />
</template>

This will give you access to the following:

NameType
emblaRefRef<HTMLElement | null>
emblaApiRef<EmblaCarouselType | null>

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    carousel: {
      slots: {
        root: 'relative focus:outline-none',
        viewport: 'overflow-hidden',
        container: 'flex items-start',
        item: 'min-w-0 shrink-0 basis-full',
        controls: '',
        arrows: '',
        prev: 'absolute rounded-full',
        next: 'absolute rounded-full',
        dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3',
        dot: [
          'cursor-pointer size-3 bg-[var(--ui-border-accented)] rounded-full',
          'transition'
        ]
      },
      variants: {
        orientation: {
          vertical: {
            container: 'flex-col -mt-4',
            item: 'pt-4',
            prev: '-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
            next: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90'
          },
          horizontal: {
            container: 'flex-row -ms-4',
            item: 'ps-4',
            prev: '-start-12 top-1/2 -translate-y-1/2',
            next: '-end-12 top-1/2 -translate-y-1/2'
          }
        },
        active: {
          true: {
            dot: 'bg-[var(--ui-border-inverted)]'
          }
        }
      },
      defaultVariants: {
        orientation: 'horizontal'
      }
    }
  }
})