Skip to content
OTFotf
All posts

shadcn's carousel runs on Embla, and autoplay is a plugin you add

D
DaveAuthor
6 min read
shadcn's carousel runs on Embla, and autoplay is a plugin you add

A carousel that moves on its own is easy to build and hard to read. shadcn's carousel runs on Embla, and its autoplay is a plugin you add on purpose, so the decision to move the content is yours to make and to justify.

What the shadcn page covers

The page describes the component as "A carousel with motion and swipe built using Embla." The install command is pnpm dlx shadcn@latest add carousel, with tabs for npm, yarn and bun. The usage example imports Carousel, CarouselContent, CarouselItem, CarouselPrevious and CarouselNext from @/components/ui/carousel. The API section refers to a CarouselApi type from the same path.

The page says: "You can use the plugins prop to add plugins to the carousel." Its autoplay example imports Autoplay from embla-carousel-autoplay and passes Autoplay({ delay: 2000 }) in the plugins array. The only autoplay option the page shows is the delay.

The page says that a size is "50% on small screens and 33% on larger screens." Its examples cover sizes, spacing, orientation, options, the API, events, plugins and a right-to-left layout. The Options example includes loop: true in its options object. The page does not describe touch or drag behavior beyond the intro's word "swipe," and it points to the Embla documentation for more. The rules below are ours, and they are labelled as ours.

Build a row of slides

Each slide is a CarouselItem inside a CarouselContent, and the previous and next controls sit beside them. The sketch below sets loop through the opts prop, which the page's options example also does:

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"

const slides = ["First", "Second", "Third"]

export function FeatureCarousel() {
  return (
    <Carousel opts={{ loop: true }}>
      <CarouselContent>
        {slides.map((slide) => (
          <CarouselItem key={slide}>
            <div className="p-6">{slide}</div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
    </Carousel>
  )
}

Put real content in each slide, not a placeholder. A slide that says only its number gives the reader nothing to decide with, and the carousel becomes decoration.

11 production screens. Login and database wired.

The SaaS Dashboard Kit includes login, a database, and a working dashboard. Live demo at saas.otf-kit.dev.

See the live demo

Add autoplay as a plugin

The plugin is the only route the page shows for autoplay, so add it through the plugins prop. Install the Embla autoplay package the page imports, then pass the plugin with a delay:

import Autoplay from "embla-carousel-autoplay"

<Carousel plugins={[Autoplay({ delay: 2000 })]}>

Two seconds per slide suits a short tour of features. A longer slide needs a longer delay, and a slide with a paragraph of text needs the reader to have time to read it before it moves on.

Luna's pencil touches a glowing play icon with cards behind it

Give the reader a pause control

Our rule is that any content which moves by itself needs a visible way to stop. The Web Content Accessibility Guidelines ask for a way to pause, stop or hide moving content that starts on its own and runs for more than five seconds, so a carousel that rotates forever needs that control. Put a real button beside the slides, label it "Pause", and make it toggle to "Play" when the motion stops.

Do not rely on a reader moving the mouse away or leaving the page to stop the motion. The pause control should be the first thing a keyboard user reaches after the slides, and its state should match what the screen shows.

Dex and Luna at a split panel: a row of cards on one side, a play and pause control on the other

Size slides for a phone

The page's sizes example uses half the width on small screens and a third on larger ones. On a phone, that shows two slides at once, with the rest of the row off the edge. Keep that hint. A carousel whose slide fills the whole phone screen looks like a single static card, and readers do not know the other slides exist.

Check the spacing between slides as well. Too little spacing makes neighboring slides look like one wide block, and the boundary between them disappears.

Keep critical information out of the motion

Do not put the only copy of a key fact inside a slide that moves away after two seconds. A reader who looks away, or who uses a screen reader, may never reach it. Repeat the important facts in the page's static text, and use the carousel for supporting material such as screenshots, quotes or feature highlights.

If a slide holds a call to action, make it a normal link or button that stays in the page's reading order. A button that disappears when the slide moves is a button some readers never press.

Test the swipe, the keyboard and the motion

Check these on the real page, not only on the component:

  • Swipe on a phone. The slides should move with the finger and settle on a full slide, not halfway between two.
  • Use the previous and next controls with the keyboard. Each should take focus, and Enter should move the row.
  • With loop on, move past the last slide. The row should wrap to the first slide, and the controls should still respond.
  • Press the pause control. The motion should stop and stay stopped until the reader starts it again.
  • Turn on the reduced-motion setting in your operating system. Decide whether the carousel should stop by itself in that case, then make the page do what you decided.

The last check is ours. The page does not describe reduced motion, so verify the behavior yourself rather than assuming the component handles it.

Where OTF stands

If you want a carousel already built into a product, we have to be careful about what we say. The Arcade Games Showcase Kit page mentions a "featured hero carousel" in its Discover and play section, and lists "Hero carousels" in its feature checklist. Those lines describe a feature of that kit. They do not say the kit uses the shadcn carousel component, so this article does not claim that it does.

We checked the SaaS Dashboard, Booking and Fitness and Wellness kit pages for carousel, slider and swipe terms, and none of them names one. The pricing page lists the Full-stack kits at $99 each and the Everything Bundle at $149, both as one-time payments, and it offers a live demo before you buy. Check each kit's repository for the components it ships before you rely on any of them. For a page with a hero section that might hold a carousel, see the breadcrumb guide, and for a settings panel beside content, see the dialog and sheet guide. If you are choosing a starting point for a product, the pricing page is the place to compare kits side by side: OTF pricing.

Sources

design-systemtemplates
OTF SaaS Dashboard Kit

Ship the product, not the setup.

  • 11 production screens — auth, team, analytics, and settings
  • Login and a real database are wired in
  • AI configs pre-tuned so your agent extends instead of regenerates
Need more than components?

Full-stack kits.
Pay once, own the code.

SaaS Dashboard and Fitness include authentication and a database. Arcade is browse-first with a local demo session and no backend sign-in by default. Booking is in Preview; Marketplace is coming soon and not currently sold. The Bundle includes the three live kits.

Everything Bundle — $149See full pricing

Get the free AI configs pack

Pre-tuned AI configs for Cursor, Claude, and Lovable — drop them in and your AI tool instantly understands your project.

No spam. Unsubscribe any time.

Prefer the free SDK? Star it on GitHub →