---
title: "Carousel"
description: "A carousel with motion and swipe built using Embla."
canonical_url: "https://vinicunca.dev/pohon/components/carousel"
---
# Carousel

> A carousel with motion and swipe built using Embla.

## Usage

Use the Carousel component to display a list of items in a carousel.

```vue [CarouselExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    loop
    arrows
    :autoplay="{ delay: 2000 }"
    wheel-gestures
    :prev="{ variant: 'solid' }"
    :next="{ variant: 'solid' }"
    :items="items"
    :ui="{
      item: 'basis-1/3 ps-0',
      prev: 'sm:start-8',
      next: 'sm:end-8',
      container: 'ms-0',
    }"
  >
    <img
      :src="item"
      width="320"
      height="320"
    >
  </PCarousel>
</template>
```

> [!NOTE]
> 
> Use your mouse to drag the carousel horizontally on desktop.

### Items

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

```vue [CarouselItemsExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

You can also pass an array of objects with the following properties:

- `class?: any`
- `ui?: { item?: ClassNameValue }`

You can control how many items are visible by using the [`basis`](https://unocss.dev/docs/flex-basis) / [`width`](https://unocss.dev/docs/width) utility classes on the `item`:

```vue [CarouselItemsMultipleExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img
      :src="item"
      width="234"
      height="234"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

### Orientation

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

> [!NOTE]
> 
> Use your mouse to drag the carousel vertically on desktop.

```vue [CarouselOrientationExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    orientation="vertical"
    :items="items"
    :ui="{ container: 'h-[336px]' }"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

> [!CAUTION]
> 
> You need to specify a `height` on the container in vertical orientation.

### Arrows

Use the `arrows` prop to display prev and next buttons.

```vue [CarouselArrowsExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    arrows
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

### Prev / Next

Use the `prev` and `next` props to customize the prev and next buttons with any [Button](https://vinicunca.dev/pohon/components/button) props.

```vue [CarouselPrevNextExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    arrows
    :prev="{ color: 'primary' }"
    :next="{ variant: 'solid' }"
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

### Prev / Next Icons

Use the `prev-icon` and `next-icon` props to customize the buttons [Icon](https://vinicunca.dev/pohon/components/icon). Defaults to `i-lucide-arrow-left` / `i-lucide-arrow-right`.

```vue [CarouselPrevNextIconExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    arrows
    :prev-icon="prevIcon"
    :next-icon="nextIcon"
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

**Nuxt:**

> [!TIP]
> See: /pohon/getting-started/integrations/icons/nuxt#theme
> 
> You can customize these icons globally in your `app.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key.

**Vue:**

> [!TIP]
> See: /pohon/getting-started/integrations/icons/vue#theme
> 
> You can customize these icons globally in your `vite.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.

```vue [CarouselDotsExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    dots
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

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

```vue [CarouselDotsMultipleExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    dots
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

## Plugins

The Carousel component implements the official [Embla Carousel plugins](https://www.embla-carousel.com/docs/v8/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](https://www.embla-carousel.com/docs/v8/plugins/autoplay).

```vue [CarouselAutoplayExample.vue]
<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>
  <PCarousel
    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"
      loading="lazy"
    >
  </PCarousel>
</template>
```

> [!NOTE]
> 
> 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](https://www.embla-carousel.com/docs/v8/plugins/auto-scroll).

```vue [CarouselAutoScrollExample.vue]
<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>
  <PCarousel
    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"
      loading="lazy"
    >
  </PCarousel>
</template>
```

> [!NOTE]
> 
> 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](https://www.embla-carousel.com/docs/v8/plugins/auto-height).

```vue [CarouselAutoHeightExample.vue]
<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>
  <PCarousel
    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="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

> [!NOTE]
> 
> 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](https://www.embla-carousel.com/docs/v8/plugins/class-names).

```vue [CarouselClassNamesExample.vue]
<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>
  <PCarousel
    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"
      loading="lazy"
    >
  </PCarousel>
</template>
```

> [!NOTE]
> 
> 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](https://www.embla-carousel.com/docs/v8/plugins/fade).

```vue [CarouselFadeExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    fade
    arrows
    dots
    :items="items"
    class="mx-auto max-w-xs w-full"
  >
    <img
      :src="item"
      width="320"
      height="320"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</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](https://www.embla-carousel.com/docs/v8/plugins/wheel-gestures).

> [!NOTE]
> 
> Use your mouse wheel to scroll the carousel.

```vue [CarouselWheelGesturesExample.vue]
<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>
  <PCarousel
    v-slot="{ item }"
    loop
    wheel-gestures
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img
      :src="item"
      width="234"
      height="234"
      class="rounded-lg"
      loading="lazy"
    >
  </PCarousel>
</template>
```

## Examples

### With thumbnails

You can use the [`scrollTo`](https://www.embla-carousel.com/docs/v8/api/methods#scrollto) method on [`emblaApi`](#expose) to display thumbnails under the carousel that navigate to a specific slide.

```vue [CarouselThumbnailsExample.vue]
<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',
];

const carousel = useTemplateRef('carousel');
const activeIndex = ref(0);

function onClickPrev() {
  activeIndex.value--;
}
function onClickNext() {
  activeIndex.value++;
}
function onSelect(index: number) {
  activeIndex.value = index;
}

function select(index: number) {
  activeIndex.value = index;

  carousel.value?.emblaApi?.scrollTo(index);
}
</script>

<template>
  <div class="flex-1 w-full">
    <PCarousel
      ref="carousel"
      v-slot="{ item }"
      arrows
      :items="items"
      :prev="{ onClick: onClickPrev }"
      :next="{ onClick: onClickNext }"
      class="mx-auto max-w-xs w-full"
      @select="onSelect"
    >
      <img
        :src="item"
        width="320"
        height="320"
        class="rounded-lg"
        loading="lazy"
      >
    </PCarousel>

    <div class="mx-auto pt-4 flex gap-1 max-w-xs justify-between">
      <div
        v-for="(item, index) in items"
        :key="index"
        class="opacity-25 size-11 transition-opacity hover:opacity-100"
        :class="{ 'opacity-100': activeIndex === index }"
        @click="select(index)"
      >
        <img
          :src="item"
          width="44"
          height="44"
          class="rounded-lg"
          loading="lazy"
        >
      </div>
    </div>
  </div>
</template>
```

## API

### Props

```ts
/**
 * Props for the Carousel component
 */
interface CarouselProps {
}
```

### Slots

```ts
/**
 * Slots for the Carousel component
 */
interface CarouselSlots {
}
```

### Emits

No events available for this component.

### Expose

You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref).

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

<template>
  <PCarousel ref="carousel" />
</template>
```

This will give you access to the following:

| Name | Type |
| --- | --- |
| `emblaRef` | `Ref<HTMLElement \| null>` |
| `emblaApi` | [`Ref<EmblaCarouselType \| null>`](https://www.embla-carousel.com/docs/v8/api/methods#typescript) |

## Theme

```ts [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-background-accented rounded-full outline-border-inverted/25 focus-visible:outline-3',
          'transition'
        ]
      },
      variants: {
        orientation: {
          vertical: {
            container: 'flex-col -mt-4',
            item: 'pt-4',
            prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
            next: 'bottom-4 sm:-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-4 sm:-start-12 top-1/2 -translate-y-1/2',
            next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2'
          }
        },
        active: {
          true: {
            dot: 'data-[state=active]:bg-background-inverted'
          }
        }
      }
    }
  }
})
```

## Changelog

See commit history for [component](https://github.com/vinicunca/pohon/commits/main/src/runtime/components/Carousel.vue) and [theme](https://github.com/vinicunca/pohon/commits/main/src/theme/carousel.ts).

---

- [Embla](https://www.embla-carousel.com/docs/v8/api)
- [GitHub](https://github.com/vinicunca/pohon/blob/main/src/runtime/components/Carousel.vue)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
