Carousel
carouseluiHorizontal or vertical scroller with previous/next controls. Built on embla-carousel (the same engine as the React twin) and wired through Svelte context. Drop in images, cards, or any custom slide content.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/carousel.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/carousel.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/carousel.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/carousel.jsonhttps://uipkge.dev/r/svelte/carousel.jsonInstalls to:src/lib/components/ui/carousel/Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
orientation | 'horizontal''vertical' | horizontal | optional |
optsEmbla options (`loop`, `align`, `slidesToScroll`, `dragFree`, …). React twin: `opts`. | EmblaOptionsType | — | optional |
pluginsEmbla plugins (e.g. `Autoplay()`). React twin: `plugins`. | EmblaPluginType[] | — | optional |
loopShorthand for `opts.loop`. An explicit `opts.loop` wins. | boolean | false | optional |
valueControlled active slide index. Two-way bindable (`bind:value`). | number | — | optional |
apiThe live embla engine. Two-way bindable (`bind:api`) — the Svelte equivalent of React's `setApi`. | CarouselApi | undefined | — | optional |
setApiReact-parity callback, fired when the engine initializes. Prefer `bind:api`. | (api: CarouselApi) => void | — | optional |
ref | HTMLDivElement | null | — | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
CarouselOptionsinterface CarouselOptions {
/** Embla options (`loop`, `align`, `slidesToScroll`, `dragFree`, …). React twin: `CarouselProps['opts']`. */
opts?: EmblaOptionsType
/** Embla plugins (e.g. `Autoplay()`). React twin: `CarouselProps['plugins']`. */
plugins?: EmblaPluginType[]
orientation?: 'horizontal' | 'vertical'
/** Shorthand for `opts.loop`. An explicit `opts.loop` wins. Kept from the scroll-snap era. */
loop?: boolean
}npm dependencies
Includes
Files installed (11)
src/lib/components/ui/carousel/Carousel.svelte5 kB
<!-- Carousel Component A flexible carousel/slider for cycling through content, built on `embla-carousel` (the same engine as the React twin's `embla-carousel-react`). All parts are wired through Svelte context that holds the shared `CarouselState`. @example - Basic carousel with images <Carousel> <CarouselContent> {#each images as img (img.id)} <CarouselItem> <img src={img.src} alt={img.alt} class="h-full w-full object-cover" /> </CarouselItem> {/each} </CarouselContent> </Carousel> @example - Carousel with navigation controls <Carousel opts={{ loop: true }}> <CarouselContent> {#each slides as slide (slide.id)} <CarouselItem> <SlideContent content={slide} /> </CarouselItem> {/each} </CarouselContent> <CarouselFooter> <CarouselPrevious /> <CarouselIndicators /> <CarouselNext /> </CarouselFooter> </Carousel> @example - Vertical carousel <Carousel orientation="vertical"> <CarouselContent> {#each items as item (item.id)} <CarouselItem> {item} </CarouselItem> {/each} </CarouselContent> </Carousel> @example - Controlled index + engine access <Carousel bind:value={activeIndex} bind:api={embla}> ... </Carousel> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' import type { EmblaOptionsType, EmblaPluginType } from 'embla-carousel' import type { CarouselApi } from './useCarousel.svelte' export interface CarouselProps extends HTMLAttributes<HTMLDivElement> { /** Embla options (`loop`, `align`, `slidesToScroll`, `dragFree`, …). React twin: `opts`. */ opts?: EmblaOptionsType /** Embla plugins (e.g. `Autoplay()`). React twin: `plugins`. */ plugins?: EmblaPluginType[] orientation?: 'horizontal' | 'vertical' /** Shorthand for `opts.loop`. An explicit `opts.loop` wins. */ loop?: boolean /** Controlled active slide index. Two-way bindable (`bind:value`). */ value?: number /** The live embla engine. Two-way bindable (`bind:api`) — the Svelte equivalent of React's `setApi`. */ api?: CarouselApi | undefined /** React-parity callback, fired when the engine initializes. Prefer `bind:api`. */ setApi?: (api: CarouselApi) => void ref?: HTMLDivElement | null } </script> <script lang="ts"> import { setContext } from 'svelte' import { cn } from '$lib/utils' import { useCarousel, CAROUSEL_CONTEXT_KEY } from './useCarousel.svelte' import { carouselVariants } from './carousel.variants' let { class: className, opts, plugins, orientation = 'horizontal', loop = false, value = $bindable(0), api = $bindable(undefined), setApi, children, ref = $bindable(null), onkeydowncapture, ...restProps }: CarouselProps = $props() // Initial capture is intentional: opts/plugins/orientation/loop sync into // the live engine via the $effect below (which re-inits embla on change). // svelte-ignore state_referenced_locally const carousel = useCarousel({ opts, plugins, orientation, loop }) // Provide to children setContext(CAROUSEL_CONTEXT_KEY, carousel) // Sync options into the shared state (re-inits the engine when attached). $effect(() => { carousel.orientation = orientation carousel.syncOptions({ opts, plugins, loop }) }) // Publish the engine outward (bindable + React-style callback). $effect(() => { const engine = carousel.api if (engine && api !== engine) { api = engine setApi?.(engine) } else if (!engine && api !== undefined) { api = undefined } }) // Controlled-index support: parent writes `value` → engine jumps (no // animation, so mount/controlled updates never visibly sweep past slides)… $effect(() => { if (carousel.api && value !== carousel.selectedIndex) { carousel.scrollTo(value, false) } }) // …and engine selection flows back to `value`. $effect(() => { const selected = carousel.selectedIndex if (selected !== value) { value = selected } }) // Arrow-key navigation — mirrors React's onKeyDownCapture. type OnKeyDownCapture = NonNullable<HTMLAttributes<HTMLDivElement>['onkeydowncapture']> const handleKeyDownCapture: OnKeyDownCapture = (e) => { onkeydowncapture?.(e) if (e.defaultPrevented) return const isVertical = orientation === 'vertical' if ((!isVertical && e.key === 'ArrowLeft') || (isVertical && e.key === 'ArrowUp')) { e.preventDefault() carousel.scrollPrev() } else if ((!isVertical && e.key === 'ArrowRight') || (isVertical && e.key === 'ArrowDown')) { e.preventDefault() carousel.scrollNext() } } </script> <div bind:this={ref} data-uipkge data-slot="carousel" data-orientation={orientation} class={cn(carouselVariants({ orientation }), className)} role="region" aria-roledescription="carousel" aria-label="Carousel" {...restProps} onkeydowncapture={handleKeyDownCapture} > {@render children?.()} </div>src/lib/components/ui/carousel/CarouselContent.svelte2.4 kB
<!-- CarouselContent The embla viewport for carousel items. Mirrors the React twin's two-layer structure: the outer div is the viewport the engine attaches to (fixed scroll/snap classes), the inner div is the flex track that carries consumer `class` / props / `ref`. @example <CarouselContent> {#each slides as slide (slide.id)} <CarouselItem> <img src={slide.src} alt={slide.alt} /> </CarouselItem> {/each} </CarouselContent> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CarouselContentProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { CAROUSEL_CONTEXT_KEY, type CarouselState } from './useCarousel.svelte' let { class: className, children, ref = $bindable(null), ...restProps }: CarouselContentProps = $props() const carousel = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) let viewport: HTMLDivElement | null = $state(null) $effect(() => { if (viewport && carousel) { carousel.attach(viewport) return () => carousel.detach() } }) const isHorizontal = $derived(carousel?.orientation !== 'vertical') // Class strings are 1:1 with the React twin's inlined snap classes. const scrollClass = $derived( isHorizontal ? 'flex snap-x snap-mandatory overflow-x-auto scroll-smooth' : 'flex snap-y snap-mandatory flex-col overflow-y-auto', ) </script> <!-- svelte-ignore a11y_role_supports_aria_props: Vue twin carries the same role + aria-orientation pair --> <div bind:this={viewport} data-uipkge data-slot="carousel-content" class={cn( scrollClass, 'relative h-full w-full', '[-ms-overflow-style:none] [scrollbar-width:none]', '[&::-webkit-scrollbar]:hidden', )} aria-orientation={carousel?.orientation} role="group" > <div bind:this={ref} class={cn( 'flex', // w-full clamps the track to the viewport width. Without it the track's // min-width:auto sizes to the slides' intrinsic content and overflows the // viewport, so basis-full slides resolve against the wider track and // render too big. Keeps the two frameworks 1:1. isHorizontal ? 'w-full' : 'flex-col', className, )} {...restProps} > {@render children?.()} </div> </div>src/lib/components/ui/carousel/CarouselFooter.svelte0.7 kB
<!-- CarouselFooter Wrapper for carousel footer content (pagination, indicators). @example <CarouselFooter> <CarouselIndicators /> </CarouselFooter> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CarouselFooterProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CarouselFooterProps = $props() </script> <div bind:this={ref} data-uipkge data-slot="carousel-footer" class={cn('flex items-center justify-between px-1 pt-2', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/carousel/CarouselHeader.svelte0.7 kB
<!-- CarouselHeader Wrapper for carousel header content (title, controls). @example <CarouselHeader> <h3>Featured Products</h3> </CarouselHeader> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CarouselHeaderProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CarouselHeaderProps = $props() </script> <div bind:this={ref} data-uipkge data-slot="carousel-header" class={cn('flex items-center justify-between px-1 pb-2', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/carousel/CarouselIndicators.svelte1.5 kB
<!-- CarouselIndicators Pagination dots showing current slide position. Clicking a dot navigates to that slide. @example <CarouselIndicators /> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CarouselIndicatorsProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { CAROUSEL_CONTEXT_KEY, type CarouselState } from './useCarousel.svelte' let { class: className, ref = $bindable(null), ...restProps }: CarouselIndicatorsProps = $props() const carousel = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) </script> {#if carousel?.api} <div bind:this={ref} data-uipkge data-slot="carousel-indicators" class={cn('flex items-center justify-center gap-1.5 py-2', className)} role="tablist" aria-label="Carousel navigation" {...restProps} > {#each carousel.scrollSnaps as _, index (index)} <button type="button" role="tab" aria-label={`Go to slide ${index + 1}`} aria-selected={index === carousel.selectedIndex} class={cn( 'h-2 w-2 rounded-full transition-colors duration-200', index === carousel.selectedIndex ? 'bg-primary w-6' : 'bg-muted-foreground/30 hover:bg-muted-foreground/50', )} onclick={() => carousel.scrollTo(index, true)} ></button> {/each} </div> {/if}src/lib/components/ui/carousel/CarouselItem.svelte1.1 kB
<!-- CarouselItem Individual slide within the carousel. Each item snaps into view when scrolled. @example <CarouselItem> <img src="/img1.jpg" alt="Slide 1" /> </CarouselItem> --> <script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CarouselItemProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { carouselItemVariants } from './carousel.variants' import { CAROUSEL_CONTEXT_KEY, type CarouselState } from './useCarousel.svelte' let { class: className, children, ref = $bindable(null), ...restProps }: CarouselItemProps = $props() const carousel = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) const itemClass = $derived(carouselItemVariants({ orientation: carousel?.orientation ?? 'horizontal' })) </script> <div bind:this={ref} data-uipkge data-slot="carousel-item" class={cn(itemClass, 'snap-start', 'relative', className)} role="group" aria-roledescription="slide" {...restProps} > {@render children?.()} </div>src/lib/components/ui/carousel/CarouselNext.svelte1.6 kB
<!-- CarouselNext Navigation button to go to the next slide. @example <CarouselNext label="Next slide" /> --> <script lang="ts" module> import type { HTMLButtonAttributes } from 'svelte/elements' export interface CarouselNextProps extends HTMLButtonAttributes { label?: string ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronRight } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button } from '$lib/components/ui/button' import { CAROUSEL_CONTEXT_KEY, type CarouselState } from './useCarousel.svelte' let { class: className, label = 'Next slide', children, ref = $bindable(null), ...restProps }: CarouselNextProps = $props() const carousel = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) const isHorizontal = $derived(carousel?.orientation !== 'vertical') </script> <Button type="button" variant="outline" size="icon" bind:ref class={cn( 'absolute z-10 size-8 shrink-0 rounded-full', 'bg-background/80 border shadow-md backdrop-blur-sm', 'hover:bg-accent hover:text-accent-foreground', 'disabled:pointer-events-none disabled:opacity-50', isHorizontal ? 'top-1/2 -right-3 -translate-y-1/2' : '-bottom-3 left-1/2 -translate-x-1/2 rotate-90', className, )} disabled={!carousel?.canScrollNext} aria-label={label} onclick={() => carousel?.scrollNext()} {...restProps} > {#if children} {@render children()} {:else} <ChevronRight class="size-4" aria-hidden="true" /> {/if} </Button>src/lib/components/ui/carousel/CarouselPrevious.svelte1.6 kB
<!-- CarouselPrevious Navigation button to go to the previous slide. @example <CarouselPrevious label="Previous slide" /> --> <script lang="ts" module> import type { HTMLButtonAttributes } from 'svelte/elements' export interface CarouselPreviousProps extends HTMLButtonAttributes { label?: string ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronLeft } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button } from '$lib/components/ui/button' import { CAROUSEL_CONTEXT_KEY, type CarouselState } from './useCarousel.svelte' let { class: className, label = 'Previous slide', children, ref = $bindable(null), ...restProps }: CarouselPreviousProps = $props() const carousel = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) const isHorizontal = $derived(carousel?.orientation !== 'vertical') </script> <Button type="button" variant="outline" size="icon" bind:ref class={cn( 'absolute z-10 size-8 shrink-0 rounded-full', 'bg-background/80 border shadow-md backdrop-blur-sm', 'hover:bg-accent hover:text-accent-foreground', 'disabled:pointer-events-none disabled:opacity-50', isHorizontal ? 'top-1/2 -left-3 -translate-y-1/2' : '-top-3 left-1/2 -translate-x-1/2 rotate-90', className, )} disabled={!carousel?.canScrollPrev} aria-label={label} onclick={() => carousel?.scrollPrev()} {...restProps} > {#if children} {@render children()} {:else} <ChevronLeft class="size-4" aria-hidden="true" /> {/if} </Button>src/lib/components/ui/carousel/carousel.variants.ts1.3 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts` or inline in the component) so `Carousel.svelte` / * `CarouselItem.svelte` can import them without creating a circular * dependency back through the index. * * The class strings are 1:1 with the React twin's inlined orientation * classes (`relative overflow-hidden` + `w-full` / `h-full flex-col` on the * root; `flex shrink-0 grow-0 basis-full flex-col` + `w-full` / `h-full` on * the item). The `snap-*` utilities stay inlined in `CarouselContent.svelte` * / `CarouselItem.svelte`, exactly where React inlines them. */ export const carouselVariants = cva('relative overflow-hidden', { variants: { orientation: { horizontal: 'w-full', vertical: 'h-full flex-col', }, }, defaultVariants: { orientation: 'horizontal', }, }) export const carouselItemVariants = cva('flex shrink-0 grow-0 basis-full flex-col', { variants: { orientation: { horizontal: 'w-full', vertical: 'h-full', }, }, defaultVariants: { orientation: 'horizontal', }, }) export type CarouselVariants = VariantProps<typeof carouselVariants> export type CarouselItemVariants = VariantProps<typeof carouselItemVariants>src/lib/components/ui/carousel/useCarousel.svelte.ts6.6 kB
/** * useCarousel * * Embla-backed carousel state for Svelte 5 runes. Mirrors the React twin * (`embla-carousel-react` + `useCarousel` context) 1:1: `Carousel.svelte` * owns the options and shares the returned state with its parts via Svelte * context; `CarouselContent` attaches the embla viewport, which creates the * engine. * * Why the framework-agnostic `embla-carousel` core instead of the * `embla-carousel-svelte` action wrapper: the wrapper's * `use:emblaCarouselSvelte` + `on:emblainit` custom-event pattern is Svelte-4 * era (`on:` is deprecated in runes mode) and splits awkwardly across the * Carousel (owns opts/plugins) / CarouselContent (owns the viewport node) * boundary. The core is the same engine `embla-carousel-react` wraps, so * behavior (`loop`, `align`, `slidesToScroll`, `dragFree`, plugins such as * Autoplay) is identical across frameworks. * * Must be called during component initialization (like the Vue composable): * it registers an `onDestroy` cleanup. */ import EmblaCarousel, { type EmblaCarouselType, type EmblaOptionsType, type EmblaPluginType } from 'embla-carousel' import { getContext, onDestroy } from 'svelte' /** The live embla engine. React twin: `CarouselApi` in `carousel.tsx`. */ export type CarouselApi = EmblaCarouselType export interface CarouselOptions { /** Embla options (`loop`, `align`, `slidesToScroll`, `dragFree`, …). React twin: `CarouselProps['opts']`. */ opts?: EmblaOptionsType /** Embla plugins (e.g. `Autoplay()`). React twin: `CarouselProps['plugins']`. */ plugins?: EmblaPluginType[] orientation?: 'horizontal' | 'vertical' /** Shorthand for `opts.loop`. An explicit `opts.loop` wins. Kept from the scroll-snap era. */ loop?: boolean } export const CAROUSEL_CONTEXT_KEY = 'carousel' export class CarouselState { /** The live embla engine once `CarouselContent` has attached, else `undefined`. `$state.raw` — never proxy an external class instance. */ api = $state.raw<EmblaCarouselType | undefined>(undefined) canScrollPrev = $state(false) canScrollNext = $state(false) selectedIndex = $state(0) scrollSnaps = $state<number[]>([]) orientation = $state<'horizontal' | 'vertical'>('horizontal') #viewport: HTMLElement | null = null #opts: EmblaOptionsType = {} #plugins: EmblaPluginType[] | undefined = undefined #onSelect = () => this.#syncFromApi() constructor(options: CarouselOptions = {}) { this.orientation = options.orientation ?? 'horizontal' this.#storeOptions(options) } /** Back-compat alias for the scroll-snap era's `activeIndex`. Prefer `selectedIndex`. */ get activeIndex(): number { return this.selectedIndex } /** Back-compat alias for the scroll-snap era's `loop`. */ get loop(): boolean { return !!this.#opts.loop } set loop(value: boolean) { this.syncOptions({ loop: value }) } /** Back-compat manual scroll-sync method from scroll-snap era. */ updateScrollState(): void { this.#syncFromApi() } /** The embla viewport node. React twin: `carouselRef`. */ get viewport(): HTMLElement | null { return this.#viewport } get isHorizontal(): boolean { return this.orientation === 'horizontal' } #storeOptions(options: Pick<CarouselOptions, 'opts' | 'plugins' | 'loop'>): void { const nextOpts = options.opts ?? {} this.#opts = options.loop !== undefined && nextOpts.loop === undefined ? { ...nextOpts, loop: options.loop } : nextOpts this.#plugins = options.plugins } #mergedOpts(): EmblaOptionsType { // Orientation always wins over `opts.axis` — matches React (`{ ...opts, axis }`). return { ...this.#opts, axis: this.orientation === 'horizontal' ? 'x' : 'y' } } #syncFromApi(): void { const api = this.api if (!api) return // One handler covers both React effects (`onSelect` in Carousel + `onUpdate` in Indicators). this.canScrollPrev = api.canScrollPrev() this.canScrollNext = api.canScrollNext() this.scrollSnaps = api.scrollSnapList() this.selectedIndex = api.selectedScrollSnap() } /** Create the engine on `viewport`. Called by `CarouselContent` once its scrollable root mounts. */ attach(viewport: HTMLElement): void { if (this.#viewport === viewport && this.api) return this.detach() this.#viewport = viewport const api = EmblaCarousel(viewport, this.#mergedOpts(), this.#plugins) this.api = api this.#syncFromApi() api.on('select', this.#onSelect) api.on('reInit', this.#onSelect) } detach(): void { if (this.api) { this.api.off('select', this.#onSelect) this.api.off('reInit', this.#onSelect) this.api.destroy() this.api = undefined } this.#viewport = null } /** Push new opts/plugins into the live engine (re-inits embla). Called by `Carousel` when props change. */ syncOptions(options: Pick<CarouselOptions, 'opts' | 'plugins' | 'loop'>): void { this.#storeOptions(options) this.api?.reInit(this.#mergedOpts(), this.#plugins) this.#syncFromApi() } scrollPrev(): void { this.api?.scrollPrev() } scrollNext(): void { this.api?.scrollNext() } scrollTo(index: number, smooth = true): void { // Embla's 2nd arg is `jump` (instant) — the inverse of `smooth`. this.api?.scrollTo(index, !smooth) } /** * @deprecated Use `scrollPrev` (React parity). Kept for scroll-snap-era * consumers — the `smooth` arg is accepted for signature compat and ignored * (embla always animates prev/next). */ // eslint-disable-next-line @typescript-eslint/no-unused-vars scrollToPrev(smooth = true): void { this.scrollPrev() } /** * @deprecated Use `scrollNext` (React parity). Kept for scroll-snap-era * consumers — the `smooth` arg is accepted for signature compat and ignored * (embla always animates prev/next). */ // eslint-disable-next-line @typescript-eslint/no-unused-vars scrollToNext(smooth = true): void { this.scrollNext() } } export function useCarousel(options: CarouselOptions = {}): CarouselState { const state = new CarouselState(options) onDestroy(() => { state.detach() }) return state } /** Read the carousel state from any sub-part. Throws outside `<Carousel />` — mirrors React's `useCarousel`. */ export function getCarouselContext(): CarouselState { const state = getContext<CarouselState | undefined>(CAROUSEL_CONTEXT_KEY) if (!state) { throw new Error('useCarousel must be used within a <Carousel />') } return state } /** @deprecated Use `CarouselState`. Renamed to free `CarouselApi` for the live Embla engine (React parity). */ export const CarouselApiClass = CarouselState export type CarouselApiState = CarouselStatesrc/lib/components/ui/carousel/index.ts1.2 kB
export { default as Carousel, type CarouselProps } from './Carousel.svelte' export { default as CarouselContent, type CarouselContentProps } from './CarouselContent.svelte' export { default as CarouselItem, type CarouselItemProps } from './CarouselItem.svelte' export { default as CarouselPrevious, type CarouselPreviousProps } from './CarouselPrevious.svelte' export { default as CarouselNext, type CarouselNextProps } from './CarouselNext.svelte' export { default as CarouselIndicators, type CarouselIndicatorsProps } from './CarouselIndicators.svelte' export { default as CarouselHeader, type CarouselHeaderProps } from './CarouselHeader.svelte' export { default as CarouselFooter, type CarouselFooterProps } from './CarouselFooter.svelte' // Re-export variant API from the sibling file (kept separate to avoid the // Carousel.svelte <-> index.ts circular import that broke dev SSR in Vue). export { carouselVariants, carouselItemVariants, type CarouselVariants, type CarouselItemVariants, } from './carousel.variants' export { useCarousel, getCarouselContext, CarouselState, CAROUSEL_CONTEXT_KEY, type CarouselApi, type CarouselOptions, } from './useCarousel.svelte'
Raw manifest:https://uipkge.dev/r/svelte/carousel.json