Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Media

Carousel

Media

Carousel React component for Tailwind CSS v4

Audio PlayerCSV Viewer
Live props
Layout
slidesPerView
peek
gap
align
orientation
Behavior
loop
dragFree
wheel
autoplay
interval5000ms
pauseOnHover
stopOnInteraction
Appearance
arrows
arrowPosition
indicator
indicatorPosition
effect
size
radius
Content
label
State
defaultIndex0
Code
import { Carousel } from "@/components/ui/carousel"

<Carousel />
  1. Home
  2. Components
  3. Media
  4. Carousel
Pro

Carousel React component for Tailwind CSS v4

v1.1.0·Updated in 1.6.0

A swipeable React carousel on Embla for photos, cards, testimonials and onboarding, with peek, arrows, dots, thumbnails and autoplay.

Drag or swipe the slides: embla's physics carry them and they snap to the nearest slide (dragFree lets a rail glide without snapping). On a trackpad, a two-finger swipe sideways moves one slide per swipe (Shift + mouse wheel too), and the page does not go back or forward. With a mouse, round arrows sit on the photo (or beside it, or in the header); they fade out at the ends and hide when there is nothing to scroll. Click a dot, a story bar or a thumbnail to jump there; the active dot stretches on a spring. With focus anywhere inside (the viewport itself is focusable), Left and Right (Up and Down when vertical) move one slide, Home and End jump to the first and last; inputs inside keep their own arrow keys. Dots and thumbnails are one Tab stop each (focus follows the selection), slides out of view are inert so Tab never lands on a hidden link (except in dragFree rails, which scroll a focused slide into view), and an arrow that turns disabled at the end hands its focus to the other one. Autoplay fills the bars as it runs and pauses while hovered, while keyboard focus is inside, while dragging, when the tab is hidden and when the carousel is off screen; the pause / play button is always there. On a phone arrows hide, 12% of the next slide peeks, dots get 44px tap areas and vertical page scrolling is never blocked. Under reduced motion arrows jump, effects are off and autoplay starts paused.

Carousel is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add carousel, together with Button, useControllableState, Motion presets and UIProvider and the npm packages embla-carousel-react, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add carousel

Carousel is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import Carousel from @/components/ui/carousel.

import { Carousel } from "@/components/ui/carousel"

const photos = ["/demo/apartment.jpg", "/demo/sofa.jpg", "/demo/city.jpg"]

export function ListingPhotos() {
  return (
    <Carousel label="Apartment photos" indicator="counter" indicatorPosition="overlay" thumbnails={photos}>
      {photos.map((src, i) => (
        <img key={src} src={src} alt={`Photo ${i + 1}`} draggable={false} className="aspect-[4/3] w-full object-cover" />
      ))}
    </Carousel>
  )
}

Props

  • children
    ReactNode

    The slides. Each child is wrapped in a CarouselSlide (role group, "3 of 8") unless it already is one. Images inside should be draggable={false}.

  • slidesPerViewdefault "1"
    number | "auto" | { base?: number; sm?: number; md?: number; lg?: number }

    Slides visible at once. Fractions peek (1.15 shows 15% of the next slide). An object sets it by the carousel's own width (container queries: base, sm from 640px, md from 768px, lg from 1024px), so a rail in a narrow column shows fewer slides and the server layout is already right. "auto" sizes slides by their content width. The playground offers 1, 1.15, 2, 3 and auto.

  • peekdefault true
    boolean

    While the carousel is narrower than 640px (a phone), whole-number slidesPerView values show 12% of the next slide (1 becomes 1.12), so it always hints that there is more.

  • gapdefault "md"
    "none" | "sm" | "md" | "lg"

    Space between slides: 0 / 8 / 12 / 16px.

  • aligndefault "start"
    "start" | "center"

    Where the selected slide sits in the viewport. center suits the scale effect and testimonials.

  • orientationdefault "horizontal"
    "horizontal" | "vertical"

    vertical scrolls up and down (tickers, stories); give the viewport a height through classNames.viewport (320px by default).

  • loopdefault false
    boolean

    Wraps from the last slide to the first. Embla turns it off by itself when there are too few slides to fill the view.

  • dragFreedefault false
    boolean

    Momentum without snapping, for rails of chips or cards.

  • wheeldefault true
    boolean

    A two-finger trackpad swipe (or Shift + mouse wheel) along the track moves it one slide per swipe; dragFree tracks follow the swipe.

  • arrowsdefault "auto"
    "auto" | "always" | "never"

    auto shows the arrows on fine pointers only (phones swipe) and hides them when there is nothing to scroll; always shows them on touch too.

  • arrowPositiondefault "inside"
    "inside" | "outside" | "header"

    inside: round glass buttons over the middle of the viewport. outside: beside the viewport from 1024px up (inside below). header: a pair at the top end, next to the title of a rail.

  • indicatordefault "dots"
    "dots" | "bars" | "progress" | "counter" | "none"

    dots: 6px dots, the active one stretched to 18px. bars: story-like segments that autoplay fills. progress: one thin bar tracking the scroll. counter: a "3 / 8" pill.

  • indicatorPositiondefault "below"
    "below" | "overlay"

    overlay puts the indicator (and the pause button) inside the bottom of the viewport on a dark gradient, for photo galleries.

  • effectdefault "none"
    "none" | "scale" | "parallax"

    scale: slides away from the snap shrink to 0.9 and dim. parallax: media marked data-carousel-parallax shifts against the drag. Both read --slide-progress, written per scroll event (never React state); off under reduced motion.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Arrow and indicator sizes: arrows 32 / 40 / 48px, dots 5 / 6 / 8px, thumbnails 48 / 56 / 64px.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg"

    Slide and viewport corners: 0 / 12 / 16 / 24px.

  • autoplaydefault false
    boolean

    Advances every interval (our own timer, no plugin). Pauses while hovered, focused, dragged, hidden or off screen, stops at the end without loop, and always shows a pause / play button (WCAG 2.2.2).

  • intervaldefault 5000
    number

    Milliseconds per slide while autoplaying.

  • pauseOnHoverdefault true
    boolean

    Autoplay waits while a mouse is over the carousel.

  • stopOnInteractiondefault false
    boolean

    false: a drag, an arrow or a dot restarts the timer; true: autoplay stops until the play button is pressed.

  • labeldefault "Carousel"
    string

    The region's aria-label, read as "Carousel, carousel" by screen readers. Name it after its content.

  • title
    ReactNode

    A heading above the viewport; the header arrows sit beside it. It also names the region (aria-labelledby) unless label is set.

  • thumbnails
    (string | ReactNode)[]

    A synced strip under the viewport: image urls or nodes, one per slide. The selected one stays in view.

  • index
    number

    The selected snap, controlled. Pair with onIndexChange.

  • defaultIndexdefault 0
    number

    The snap shown first, uncontrolled. The playground remounts the carousel when it changes.

  • onIndexChange
    (index: number) => void

    Fires when the selected snap changes, by drag, arrow, dot, key or autoplay.

  • labels
    Partial<CarouselLabels>

    Every string: previous, next, slide(i, n) ("3 of 8"), goTo(i) ("Go to slide 3"), pause, play, carousel and slideRole (the aria-roledescriptions). English by default, DEFAULT_CAROUSEL_LABELS.

  • opts
    EmblaOptionsType

    Anything else for embla (slidesToScroll, skipSnaps, duration, containScroll...). Wins over the props above.

  • setApi
    (api: CarouselApi) => void

    Receives the embla api once it is ready.

  • classNames
    Partial<Record<"header" | "title" | "stage" | "viewport" | "container" | "slide" | "previous" | "next" | "indicator" | "dot" | "counter" | "autoplay" | "thumbnails" | "thumbnail" | "footer" | "overlay", string>>

    Extra classes for the inner parts.

Parts

  • CarouselRoot

    component

    The compound root: a <section aria-roledescription="carousel"> that owns embla, autoplay and the keyboard. Takes every behavior prop above (not the arrow / indicator layout ones), plus scrim (a dark gradient at the bottom of each slide for white controls overlaid on photos). Props: CarouselRootProps.

  • CarouselViewport

    component

    The embla viewport and its container; wraps children in slides and numbers them. Props: ComponentProps<"div"> & { classNames?: { container?, slide? } }.

  • CarouselSlide

    component

    One slide: role group, data-active, inert while out of view, --slide-progress for effects. Props: ComponentProps<"div"> & { index?: number; total?: number }.

  • CarouselPrevious

    component

    The previous arrow, disabled at the start (inside arrows fade out there). Props: ButtonProps & { position?: "inside" | "outside" | "header"; visibility?: "auto" | "always" }.

  • CarouselNext

    component

    The next arrow, disabled at the end. Props: ButtonProps & { position?: "inside" | "outside" | "header"; visibility?: "auto" | "always" }.

  • CarouselDots

    component

    Dots, story bars or a progress bar; overlay is white-based for photos. Props: ComponentProps<"div"> & { variant?: "dots" | "bars" | "progress"; overlay?: boolean }.

  • CarouselCounter

    component

    The "3 / 8" pill. Props: ComponentProps<"span"> & { overlay?: boolean }.

  • CarouselAutoplayToggle

    component

    The pause / play button; renders only while autoplay is on. Props: ButtonProps & { overlay?: boolean }.

  • CarouselThumbnails

    component

    A second, drag-free embla strip synced to the selection. Props: ComponentProps<"div"> & { items: (string | ReactNode)[] }.

  • useCarousel

    hook

    State and controls of the nearest carousel, for custom controls (an onboarding Continue button). progress is the autoplay motion value, 0 to 1. Signature: () => { api, selectedIndex, snapCount, canScrollPrev, canScrollNext, scrollTo, scrollPrev, scrollNext, playing, setPlaying, progress }.

  • DEFAULT_CAROUSEL_LABELS

    constant

    The English strings for the arrows, slide names ("3 of 8"), dot names ("Go to slide 3"), autoplay pause and play, and the aria role descriptions. Spread it to translate.

  • CarouselApi

    type

    The Embla API instance, for scrollTo, events and plugins when you need more than the props.

Examples

Listing rail

Cards at 1.15 per view on phones, 2.5 and 3.5 wider; arrows in the header next to the title.

Autoplay hero

Loops every 4.5s; the story bars fill with the timer. Hover, focus or drag to pause; the button stops it.

Gallery with thumbnails

A synced strip under the photo; the counter sits on the image.

Testimonials with scale

Centered and looping; off-center slides shrink and dim as they move away.

Onboarding

Compound parts with useCarousel: Continue calls scrollNext, Get started on the last step.

Chip rail

dragFree with auto-sized slides: momentum without snapping.

Vertical ticker

Compound parts: a vertical loop every 3s, with the pause button in the header.

Dependencies

embla-carousel-reactmotionlucide-reactbuttonuse-controllable-statemotionui-config

Files

  • components/ui/carousel.tsx

FAQ

Is the React Carousel component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Carousel?

Run npx wingo-ui@latest add carousel. It also adds the Wingo UI items it builds on (Button, useControllableState, Motion presets and UIProvider) and the npm packages embla-carousel-react, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/carousel.

How does Carousel behave on phones?

On a phone arrows hide, 12% of the next slide peeks, dots get 44px tap areas and vertical page scrolling is never blocked.

See also

CardScroll AreaStep PlayerButton

Used in

Listing cardProduct galleryImage LightboxMarketplace Home PagePricing tableSeller cardOnboarding CarouselTestimonials SectionCheckout PageListing Detail Page

More React media components

PowerPoint ViewerVideo PlayerImage CropperLocation MapAudio PlayerExcel Viewer
All 17 React media components

Articles using this component

  • Embla Carousel React: Accessible Slides, No Layout Shift

    Component guides · Oct 19, 2026 · 9 min read

PreviousAudio PlayerNext CSV Viewer
3-bedroom apartment, Park Slope
Brooklyn$3,200/mo
Studio with a view, Old Town
Chicago$1,650/mo
Furnished living room, Back Bay
Boston$2,100/mo
Mountain cabin, Estes Park
Colorado$180/night
Parking spot, SoHo
New York$350/mo
Coworking desk, Pearl District
Portland$280/mo

3-bedroom apartment, Park Slope

Brooklyn · 840 sq ft · Swipe, drag or use the arrow keys

label="Carousel" · peek=true · loop=false · dragFree=false · interval=5000ms · pauseOnHover=true · stopOnInteraction=false