Carousel
A draggable, swipeable slider built on Embla — with autoplay, fade, auto-height, thumbnails, and creative scroll effects.





Usage
Carousel wraps Embla Carousel — a lightweight, dependency-free engine for dragging, snapping, and momentum — and exposes it as a small set of composable parts. Carousel sets up the engine and shares it through context; CarouselContent is the scroll viewport and track; each CarouselSlide is one snap point. Drop in CarouselPrev / CarouselNext for arrows and CarouselPagination or CarouselProgress for position feedback — each reads the engine from context, so they can sit anywhere inside the root.
Every slide is basis-full by default (one per view). Set a smaller basis-* on CarouselSlide to show several at once, and pick where they settle with align. Options that mirror Embla — loop, align, slidesToScroll, dragFree, duration, and more — are flat props on Carousel; the full option object is available through options as an escape hatch.
The arrows (CarouselPrev / CarouselNext) render an unstyled <button> by default so you own their look. Pass a Button (or any element) via render — put structural props like variant/size on that element, and use position to place the arrows inside, outside, or straddling the edge.
Examples
Default
A single slide per view with arrows overlaid inside the frame and a pagination strip below. loop lets it wrap around from the last slide back to the first.
Multiple slides (responsive)
Give each CarouselSlide a fractional basis-* to fit several per view — here basis-1/2 on small screens and basis-1/3 above. slidesToScroll="auto" advances by however many slides are fully visible, and align="start" snaps them to the leading edge.
Alignment
align controls where slides settle in the viewport — start, center, or end. Combined with a basis smaller than the viewport, center peeks the neighbouring slides on either side.
Vertical
Set orientation="vertical" to drag and snap along the Y axis — same footprint as the horizontal carousel, just scrolling top-to-bottom. The viewport needs a fixed height (here h-100 on CarouselContent); the arrows settle inside at the top and bottom edges automatically, and CarouselPagination takes orientation="vertical" to run its bullets down the side. The same 16px inter-slide gap as the horizontal carousel carries over, so slides don't butt together as you scroll.
Autoplay
Pass autoplay to advance on a timer via Embla's Autoplay plugin. Give it an object to tune the delay, and add resumeAfter extension to restart the timer a set time after the user interacts. When autoplay is running, the active pagination bullet grows into a filling progress pill.
Auto Scroll
autoScroll swaps the discrete timer for continuous marquee-style motion via the Auto Scroll plugin — pair it with loop and dragFree for an endless conveyor. Like autoplay, it accepts resumeAfter extension to restart the scroll a set time after the user interacts. autoScroll and autoplay are mutually exclusive. Auto Scroll pauses under prefers-reduced-motion.
Fade
fade cross-fades between slides instead of sliding them, using the Fade plugin. It's meant for one slide per view — ideal for a hero or a full-bleed gallery.
Auto Height
autoHeight animates the viewport to each slide's natural height as you navigate, via the Auto Height plugin — so mixed-length content doesn't clip or leave dead space. Use align="start" so slides measure from the top.
Free drag & wheel gestures
dragFree releases the snap points so the carousel glides to a momentum-based stop wherever the drag ends — a natural fit for a scrollable strip. Add wheelGestures to also scroll it with a trackpad or mouse wheel (horizontal swipes on a trackpad, vertical wheel on a mouse), via Embla's Wheel Gestures plugin.
Progress
CarouselProgress renders a scroll progress bar (or a circular ring with variant="circular"). By default it tracks drag position, and automatically switches to counting down the autoplay timer while autoplay is playing.
Thumbnails
Two carousels kept in sync: a main viewer and a strip of thumbnails. The useLinkedCarousels(mainApi, thumbsApi) hook wires each one's select event to the other, so navigating either drives both. Grab each engine with setApi, and read selectedIndex / scrollTo from useCarousel() inside a thumbnail to highlight and jump.
Synced carousels
useLinkedCarousels isn't limited to thumbnails — it links any two engines. Here a sliding image carousel drives a fading text carousel below it, so the caption cross-fades in as the image slides. The link is bidirectional, so the outside arrows move both engines in lockstep. Give the text carousel fade and draggable={false} so it only ever follows the images.
Parallax
A creative effect built on the engine's scroll event: each slide's inner layer is over-scaled and translated against the scroll, so the image drifts within its frame as you drag. Grab the api with setApi, then tween slideNodes() on every scroll — accounting for loop points from internalEngine().
Scale
The same scroll-driven tweening, applied to transform: scale and opacity: each slide shrinks and dims in proportion to its distance from the centre, so the focused slide pops. Pairs well with align="center" and a basis that peeks the neighbours.
Overlaid controls
Set light on CarouselPagination / CarouselProgress (and a light-variant Button for the arrows) when the controls sit over imagery, so they read against a dark scrim instead of the page background.
Controlled with the api
Capture the Embla instance with setApi to drive the carousel imperatively and mirror its state elsewhere. Here external buttons call goToPrev() / goToNext() and a counter subscribes to the select event to show the current position.
Next.js Image
Render slides with next/image for automatic optimization. Give each slide a fixed-aspect wrapper and use fill with a matching sizes so the image resolves the right source; mark the first slide priority since it's the LCP candidate.
RTL
Every Appica UI component supports right-to-left layouts out of the box. Set the dir attribute on a container (commonly your <html> element) so CSS logical properties resolve correctly, and wrap your tree in DirectionProvider so direction-aware behavior follows the same direction.
A horizontal carousel reverses its drag axis and starting edge, so slides flow from the right and the Previous/Next arrows swap sides. Give the arrow buttons direction-appropriate icons and aria-labels. Vertical carousels are unaffected by direction. For setup details and caveats, see the RTL guide.





API reference
Carousel is a compound component. The root creates the Embla engine and shares it through context; the parts read from it. Flat props on the root mirror Embla options and toggle its official plugins.
Carousel
The root. Creates the engine, provides context, and renders a <div role="region">.
The root also forwards remaining native <div> attributes.
CarouselContent
The scroll viewport plus the flex track that holds the slides. Renders two nested <div>s. className lands on the inner track — a common place to set the viewport height for vertical carousels (e.g. h-72).
CarouselSlide
One slide / snap point. Renders a <div role="group"> with basis-full by default — override basis-* to change how many slides show per view.
CarouselPrev / CarouselNext
The previous/next arrows. Each renders an unstyled <button> (auto-disabled at the ends) inside a positioner. Pass your own element — usually a Button — through render.
Composition (render, className) is powered by Base UI's useRender. Remaining button attributes (onClick, aria-label, …) are forwarded to the button and merged with the internal scroll handler.
CarouselPagination
A row (or column) of clickable bullets, one per snap point. Renders a <div role="tablist">; returns null when there's a single snap. The active bullet fills as an autoplay timer indicator while autoplay is playing.
CarouselProgress
A scroll/autoplay progress indicator. Renders a bar by default, or a ring with variant="circular".
useCarousel()
Hook that returns the carousel context from inside a Carousel: api (the Embla instance), navigation callbacks (scrollPrev, scrollNext, scrollTo), and reactive state (selectedIndex, scrollSnaps, canScrollPrev, canScrollNext, orientation, direction, plus autoplay state). Per-frame scroll progress is exposed as a subscribeScrollProgress / getScrollProgress pair (subscribe with useSyncExternalStore so only readers re-render — this is what CarouselProgress uses); read slides in view from api.slidesInView(). Throws if used outside a Carousel.
useLinkedCarousels(mainApi, thumbsApi)
Bidirectionally syncs two carousels' selected snaps — the classic main-plus-thumbnails pattern. Wires each engine's select event to goTo the other. Both apis may be undefined initially (while waiting on setApi); the effect re-runs once both resolve.
Accessibility
- The root is a
role="region"witharia-roledescription="carousel"and anaria-label(native attribute, default"Carousel"); each slide is arole="group"witharia-roledescription="slide". - The Accessibility plugin (on by default) makes the viewport keyboard-operable — focus it and use the arrow keys to navigate.
CarouselPrev/CarouselNextcarryaria-labels ("Previous slide"/"Next slide") and aredisabledat the ends of a non-looping carousel. When you supply arenderelement, keep an accessible name and localize it as needed.CarouselPaginationis arole="group"labelled"Choose slide to display"of bullet<button>s, each labelled"Go to slide N"witharia-currenton the active one.- Progress fills, the autoplay indicator, and scroll animations honour
prefers-reduced-motion— drag snaps instantly and Auto Scroll is disabled. - Give image slides meaningful
alttext (oralt=""for purely decorative imagery).







