Accessible Image Carousel
A free React image carousel with keyboard buttons, touch drag and pause-aware autoplay.
Customise
Code
<MediaImageCarousel
slides={[
{
id: "harbor-light",
src: "/harbour-light.jpg",
alt: "Harbour town silhouetted against a warm dusk sky",
caption: "Harbour Light, Nordvik",
width: 1600,
height: 1000,
},
{
id: "glass-ridge",
src: "/glass-ridge.jpg",
alt: "Glacial ridgeline reflected in still turquoise water",
caption: "Glass Ridge, Halden",
width: 1600,
height: 1000,
},
// ...two to ten slides total
]}
label="Featured destinations"
/>Accessible Image Carousel is a free React image carousel for Next.js and Tailwind that moves visitors through two to ten images with previous and next buttons, dot indicators, arrow keys or a thresholded touch drag, and announces every manual change to assistive technology. Built with Motion, it ships three transition styles (a sliding peeked track, a crossfade and a bounded scale), so the same slides array can read as three different carousels.
Autoplay is optional and off by default; once configured it starts only after hydration, always shows a Pause/Play button, and pauses on hover, keyboard focus, an active drag, the carousel leaving the viewport, a hidden browser tab or a reduced-motion preference, restarting a fresh full interval after any of those clears. Automatic advances update the live region quietly (aria-live="off") while every deliberate navigation announces the new slide politely. With prefers-reduced-motion enabled, autoplay never starts and every slide change swaps instantly with full keyboard and touch controls.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/media-image-carouselUsage
import { MediaImageCarousel } from "@/components/media-image-carousel";
const slides = [
{
id: "harbor-light",
src: "/harbour-light.jpg",
alt: "Harbour town silhouetted against a warm dusk sky",
caption: "Harbour Light, Nordvik",
width: 1600,
height: 1000,
},
{
id: "glass-ridge",
src: "/glass-ridge.jpg",
alt: "Glacial ridgeline reflected in still turquoise water",
caption: "Glass Ridge, Halden",
width: 1600,
height: 1000,
},
// ...two to ten slides total
];
export function Example() {
return (
<MediaImageCarousel
slides={slides}
transition="slide"
autoplayInterval={5000}
label="Featured destinations"
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| slides | { id; src; alt; caption?; width; height }[] | required | Two to ten slides with unique ids and stable dimensions used to reserve the viewport. |
| initialId | string | undefined | Initial slide id. Falls back to the first slide when omitted or unknown. |
| transition | "slide" | "crossfade" | "scale" | "slide" | Slide-change treatment. |
| gap | number | 16 | Track gap in CSS pixels for "slide" mode, clamped to 0 to 40. |
| peek | number | 0.12 | Adjacent-slide fraction shown at each edge in "slide" mode, clamped to 0 to 0.3. |
| duration | number | 0.45 | Transition duration in seconds, clamped to 0 to 0.9. |
| autoplayInterval | number | 0 | Milliseconds between automatic slides. Zero disables autoplay; any other value is clamped to 2500 to 12000. |
| loop | boolean | true | Whether previous and next wrap at the ends. |
| radius | number | 24 | Media viewport radius in CSS pixels, clamped to 0 to 44. |
| label | string | "Image carousel" | Accessible name for the carousel region. |
| onSlideChange | (id: string) => void | undefined | Called with the new slide id after an explicit or automatic slide change. |
| className | string | undefined | Additional classes merged onto the carousel region. |
Customisation
Three Tier 3 tokens retheme the carousel without touching code. --image-carousel-accent colours the active dot and the control focus ring and defaults to var(--primary); --image-carousel-panel fills the stage and controls, chaining var(--card, var(--surface)); --image-carousel-divider borders the controls and defaults to var(--border). Geometry and pacing come from the gap, peek, duration and radius props:
<MediaImageCarousel
slides={slides}
transition="scale"
radius={12}
className="[--image-carousel-accent:var(--primary-accent)]"
/>Frequently asked questions
- Is Accessible Image Carousel free for commercial use?
- Yes. Accessible Image Carousel is MIT licensed, so you can use it in personal and commercial projects without attribution.
- Is the carousel accessible and safe under reduced motion?
- Yes. The region carries the APG carousel role, each slide is a named group with required alt text and an optional figcaption, and only the active slide is exposed and focusable. Previous, next, every dot and the optional Pause/Play control are native buttons with visible focus. Touch dragging respects vertical scrolling and never traps focus. With prefers-reduced-motion enabled, autoplay never starts and slides swap instantly while every control keeps working.
- How do I change the transition style and autoplay pace?
- Set transition to slide, crossfade or scale to change how slides change without touching the slides array, and pass autoplayInterval in milliseconds (clamped to 2500 to 12000) to enable a pause-aware autoplay timer with its own visible control. Leave autoplayInterval unset for a fully manual carousel.