Free and open-source React carousel components built for modern applications and websites. These carousels are built using Embla Carousel and styled with Tailwind CSS.
Installation
You can add this carousel component using our CLI or manually:
npx untitledui@latest add carousel-base
Carousel examples
Below are examples and variations of this carousel component:
API reference
Import the parts and assemble them:
import { Carousel } from "@/components/application/carousel/carousel-base"; <Carousel.Root> <Carousel.PrevTrigger /> <Carousel.NextTrigger /> <Carousel.Content> <Carousel.Item /> </Carousel.Content> <Carousel.IndicatorGroup> <Carousel.Indicator /> </Carousel.IndicatorGroup> </Carousel.Root>
Carousel.Content
Renders a <div> element.
Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.
Carousel.Indicator
Renders a <button> element.
Carousel.IndicatorGroup
Renders a <nav> element.
Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.
Carousel.Item
Renders a <div> element.
Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.
Carousel.NextTrigger
Carousel.PrevTrigger
Carousel.Root
Renders a <div> element.
Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.
FAQs
Please refer to our frequently asked questions page for more.
Use Carousel.PrevTrigger and Carousel.NextTrigger anywhere inside Carousel.Root. They disable themselves automatically at the start and end of the carousel, and their className also accepts a function that receives { isDisabled } so you can style that state. Pass asChild to render your own button element instead of the default one.
Yes. Set orientation on Carousel.Root to "vertical" — it defaults to "horizontal". The value maps to Embla's axis option, and Carousel.Content switches to a column layout automatically.
Pass Embla options straight through with the opts prop, for example opts={{ loop: true, align: "start" }}. Embla plugins such as autoplay go in the plugins prop, which is forwarded to useEmblaCarousel unchanged.
Pass a setApi callback to Carousel.Root to capture the Embla API instance, then call methods like api.scrollTo(2) from outside. Inside the carousel you can call the useCarousel() hook instead, which exposes scrollPrev, scrollNext, canScrollPrev, canScrollNext, selectedIndex and scrollSnaps.
The examples render a PaginationDot from @/components/application/pagination/pagination-dot inside Carousel.Root and wire it to useCarousel(): page={selectedIndex + 1}, total={scrollSnaps.length} and onPageChange={(page) => api?.scrollTo(page - 1)}. The examples also pass framed, plus size="lg" on the larger carousel.
Each Carousel.Item defaults to basis-full, so override it via className — for example className="basis-1/2 md:basis-1/3" for a responsive multi-item carousel. Add spacing by putting a gap utility on Carousel.Content.
Yes, for horizontal carousels. Carousel.Root handles the Left and Right arrow keys while focus is inside it, such as on a trigger, and renders with role="region" and aria-roledescription="carousel", while each Carousel.Item renders as role="group" with aria-roledescription="slide". The triggers get default aria-label values of "Previous slide" and "Next slide". Only the Left and Right keys are wired up, so an orientation="vertical" carousel needs its own Up and Down key handling.
