UIPackage
Menu

Framework

Change language

Boilerplate repo

Carousel

carouselui

Horizontal or vertical scroller with previous/next controls. Hand-rolled `useCarousel` composable backed by native CSS scroll-snap — no external carousel library, just the browser. Drop in images, cards, or any custom slide content.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/carousel.json
Named registry:npx shadcn-vue@latest add @uipkge/carouselInstalls to:app/components/ui/carousel/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
modelValuenumber0optional
orientation
'horizontal''vertical'
'horizontal'optional
loopbooleanfalseoptional
classHTMLAttributes['class']optional

Schema

Type aliases from this item's source — use them to shape the data you pass in.

CarouselOptions
interface CarouselOptions {
  loop?: boolean
  orientation?: 'horizontal' | 'vertical'
}
UseCarouselReturn
interface UseCarouselReturn {
  activeIndex: Ref<number>
  scrollSnaps: Ref<number[]>
  canScrollPrev: Ref<boolean>
  canScrollNext: Ref<boolean>
  scrollTo: (index: number, smooth?: boolean) => void
  scrollToPrev: (smooth?: boolean) => void
  scrollToNext: (smooth?: boolean) => void
  rootRef: Ref<HTMLElement | null>
  orientation: Ref<'horizontal' | 'vertical'>
}

Files installed (11)

  • app/components/ui/carousel/Carousel.vue2.7 kB
    <!--
      Carousel Component
      
      A flexible carousel/slider for cycling through content.
      
      @example - Basic carousel with images
      <Carousel>
        <CarouselContent>
          <CarouselItem v-for="img in images" :key="img.id">
            <img :src="img.src" :alt="img.alt" class="w-full h-full object-cover" />
          </CarouselItem>
        </CarouselContent>
      </Carousel>
      
      @example - Carousel with navigation controls
      <Carousel v-model:active-index="activeIndex" :loop="true">
        <CarouselContent>
          <CarouselItem v-for="slide in slides" :key="slide.id">
            <SlideContent :content="slide" />
          </CarouselItem>
        </CarouselContent>
        <template #footer>
          <CarouselPrevious />
          <CarouselIndicators />
          <CarouselNext />
        </template>
      </Carousel>
      
      @example - Vertical carousel
      <Carousel orientation="vertical" :loop="false">
        <CarouselContent>
          <CarouselItem v-for="item in items" :key="item.id">
            {{ item }}
          </CarouselItem>
        </CarouselContent>
      </Carousel>
    -->
    <script setup lang="ts">
    import { provide, computed, watch, onMounted, onUnmounted, type HTMLAttributes } from 'vue'
    import { cn } from '@/lib/utils'
    import { useCarousel } from './useCarousel'
    import { carouselVariants } from './carousel.variants'
    
    export interface CarouselProps {
      modelValue?: number
      orientation?: 'horizontal' | 'vertical'
      loop?: boolean
      class?: HTMLAttributes['class']
    }
    
    const props = withDefaults(defineProps<CarouselProps>(), {
      modelValue: 0,
      orientation: 'horizontal',
      loop: false,
    })
    
    const emit = defineEmits<{
      'update:modelValue': [value: number]
    }>()
    
    const carousel = useCarousel({
      loop: props.loop,
      orientation: props.orientation,
    })
    
    // Sync modelValue → scroll when controlled externally
    watch(
      () => props.modelValue,
      (newVal) => {
        if (newVal !== carousel.activeIndex.value) {
          carousel.scrollTo(newVal, false)
        }
      },
    )
    
    // Emit scroll position changes back to parent
    function onScroll() {
      const newIndex = carousel.activeIndex.value
      if (newIndex !== props.modelValue) {
        emit('update:modelValue', newIndex)
      }
    }
    
    onMounted(() => {
      if (carousel.rootRef.value) {
        carousel.rootRef.value.addEventListener('scroll', onScroll, { passive: true })
      }
    })
    
    onUnmounted(() => {
      if (carousel.rootRef.value) {
        carousel.rootRef.value.removeEventListener('scroll', onScroll)
      }
    })
    
    // Provide to children
    provide('carousel', {
      ...carousel,
      orientation: computed(() => props.orientation),
      loop: computed(() => props.loop),
    })
    </script>
    
    <template>
      <div
        data-uipkge
        data-slot="carousel"
        :class="cn(carouselVariants({ orientation: props.orientation }), props.class)"
        role="region"
        aria-roledescription="carousel"
        aria-label="Carousel"
      >
        <slot />
      </div>
    </template>
    
  • app/components/ui/carousel/CarouselContent.vue1.4 kB
  • app/components/ui/carousel/CarouselFooter.vue0.5 kB
  • app/components/ui/carousel/CarouselHeader.vue0.5 kB
  • app/components/ui/carousel/CarouselIndicators.vue1.3 kB
  • app/components/ui/carousel/CarouselItem.vue1 kB
  • app/components/ui/carousel/CarouselNext.vue1.4 kB
  • app/components/ui/carousel/CarouselPrevious.vue1.5 kB
  • app/components/ui/carousel/carousel.variants.ts1.1 kB
  • app/components/ui/carousel/useCarousel.ts4 kB
  • app/components/ui/carousel/index.ts0.8 kB

Raw manifest:https://uipkge.dev/r/vue/carousel.json