diff --git a/packages/module/playground/components/ui/carousel/Carousel.vue b/packages/module/playground/components/ui/carousel/Carousel.vue
new file mode 100644
index 00000000..c88d61ad
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/Carousel.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/packages/module/playground/components/ui/carousel/CarouselContent.vue b/packages/module/playground/components/ui/carousel/CarouselContent.vue
new file mode 100644
index 00000000..f432d015
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/CarouselContent.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
diff --git a/packages/module/playground/components/ui/carousel/CarouselItem.vue b/packages/module/playground/components/ui/carousel/CarouselItem.vue
new file mode 100644
index 00000000..53ea50da
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/CarouselItem.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/packages/module/playground/components/ui/carousel/CarouselNext.vue b/packages/module/playground/components/ui/carousel/CarouselNext.vue
new file mode 100644
index 00000000..ca65f8d1
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/CarouselNext.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/packages/module/playground/components/ui/carousel/CarouselPrevious.vue b/packages/module/playground/components/ui/carousel/CarouselPrevious.vue
new file mode 100644
index 00000000..331522b7
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/CarouselPrevious.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/packages/module/playground/components/ui/carousel/index.ts b/packages/module/playground/components/ui/carousel/index.ts
new file mode 100644
index 00000000..addfe036
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/index.ts
@@ -0,0 +1,10 @@
+export { default as Carousel } from './Carousel.vue'
+export { default as CarouselContent } from './CarouselContent.vue'
+export { default as CarouselItem } from './CarouselItem.vue'
+export { default as CarouselPrevious } from './CarouselPrevious.vue'
+export { default as CarouselNext } from './CarouselNext.vue'
+export { useCarousel } from './useCarousel'
+
+export type {
+ EmblaCarouselType as CarouselApi,
+} from 'embla-carousel'
diff --git a/packages/module/playground/components/ui/carousel/interface.ts b/packages/module/playground/components/ui/carousel/interface.ts
new file mode 100644
index 00000000..99c4f1a8
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/interface.ts
@@ -0,0 +1,20 @@
+import type {
+ EmblaCarouselType as CarouselApi,
+ EmblaOptionsType as CarouselOptions,
+ EmblaPluginType as CarouselPlugin,
+} from 'embla-carousel'
+import type { HTMLAttributes, Ref } from 'vue'
+
+export interface CarouselProps {
+ opts?: CarouselOptions | Ref
+ plugins?: CarouselPlugin[] | Ref
+ orientation?: 'horizontal' | 'vertical'
+}
+
+export interface CarouselEmits {
+ (e: 'init-api', payload: CarouselApi): void
+}
+
+export interface WithClassAsProps {
+ class?: HTMLAttributes['class']
+}
diff --git a/packages/module/playground/components/ui/carousel/useCarousel.ts b/packages/module/playground/components/ui/carousel/useCarousel.ts
new file mode 100644
index 00000000..85a97ebc
--- /dev/null
+++ b/packages/module/playground/components/ui/carousel/useCarousel.ts
@@ -0,0 +1,57 @@
+import { createInjectionState } from '@vueuse/core'
+import emblaCarouselVue from 'embla-carousel-vue'
+import { onMounted, ref } from 'vue'
+import type {
+ EmblaCarouselType as CarouselApi,
+} from 'embla-carousel'
+import type { CarouselEmits, CarouselProps } from './interface'
+
+const [useProvideCarousel, useInjectCarousel] = createInjectionState(
+ ({
+ opts, orientation, plugins,
+ }: CarouselProps, emits: CarouselEmits) => {
+ const [emblaNode, emblaApi] = emblaCarouselVue({
+ ...opts,
+ axis: orientation === 'horizontal' ? 'x' : 'y',
+ }, plugins)
+
+ function scrollPrev() {
+ emblaApi.value?.scrollPrev()
+ }
+ function scrollNext() {
+ emblaApi.value?.scrollNext()
+ }
+
+ const canScrollNext = ref(true)
+ const canScrollPrev = ref(true)
+
+ function onSelect(api: CarouselApi) {
+ canScrollNext.value = api.canScrollNext()
+ canScrollPrev.value = api.canScrollPrev()
+ }
+
+ onMounted(() => {
+ if (!emblaApi.value)
+ return
+
+ emblaApi.value?.on('init', onSelect)
+ emblaApi.value?.on('reInit', onSelect)
+ emblaApi.value?.on('select', onSelect)
+
+ emits('init-api', emblaApi.value)
+ })
+
+ return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }
+ },
+)
+
+function useCarousel() {
+ const carouselState = useInjectCarousel()
+
+ if (!carouselState)
+ throw new Error('useCarousel must be used within a ')
+
+ return carouselState
+}
+
+export { useCarousel, useProvideCarousel }
diff --git a/packages/module/playground/package.json b/packages/module/playground/package.json
index 4e770f3d..9ed5f882 100644
--- a/packages/module/playground/package.json
+++ b/packages/module/playground/package.json
@@ -11,6 +11,8 @@
"@nuxtjs/tailwindcss": "^6.10.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
+ "embla-carousel": "8.0.0-rc19",
+ "embla-carousel-vue": "8.0.0-rc19",
"lucide-vue-next": "^0.276.0",
"radix-vue": "^1.3.0",
"tailwind-merge": "^2.0.0",