Carousel
Accessible, responsive, touch-friendly content carousel with autoplay, looping, multiple views, and vertical layouts.
Carousel
Carousel displays a sequence of related items in a navigable viewport. It supports horizontal and vertical layouts, slide/fade/scale effects, multiple visible slides, looping, autoplay, pointer dragging, keyboard navigation, dots, thumbnails, progress, lazy images, disabled items, and custom rendering.
See the live Carousel showcase.
Basic usage
import { Carousel } from '@mosmox/ui';
const stories = [
{ key: 'one', title: 'First story', description: 'Short supporting copy.' },
{ key: 'two', title: 'Second story', description: 'Another item in the sequence.' },
];
<Carousel aria-label="Featured stories" items={stories} />items may contain text, images, or arbitrary React content. Every item requires a stable, unique key. The built-in renderer uses eyebrow, badge, title, description, content, image, and alt. For completely custom markup, use renderItem.
Variants and effects
<Carousel items={stories} variant="default" effect="slide" />
<Carousel items={stories} variant="overlay" effect="fade" />
<Carousel items={products} variant="cards" effect="scale" slidesPerView={3} />
<Carousel items={articles} variant="minimal" />
<Carousel items={recommendations} variant="peek" />| Option | Values | Description |
|---|---|---|
variant | default | Bordered content cards; default. |
overlay | Image-focused cards with gradient content overlay. | |
minimal | Removes card chrome for editorial layouts. | |
cards | Adds elevation to each slide. | |
peek | Shows part of the next slide as a discovery cue. | |
effect | slide | Translates the track; default effect. |
fade | Fades between full-width slides. | |
scale | Scales the active slide and reduces opacity on inactive slides. |
Navigation controls
<Carousel
aria-label="Travel destinations"
items={destinations}
showArrows
showDots
showProgress
showThumbnails
thumbnailSize={72}
previousLabel="Previous destination"
nextLabel="Next destination"
dotLabel={(index, item) => `Open ${item.title ?? `slide ${index + 1}`}`}
/>showArrowsrenders Previous and Next buttons; defaulttrue.showDotsrenders tab-like position buttons; defaulttrue.showProgressrenders a progress bar; defaultfalse.showThumbnailsrenders image thumbnail tabs; defaultfalse.thumbnailSizesets thumbnail width and height in pixels; default64.previousLabelandnextLabelprovide accessible arrow labels.dotLabelcreates accessible labels for dots and thumbnails.
The controls use the same navigation state as dragging and keyboard input. With loop={false}, the arrow at the first or last valid position becomes disabled.
Autoplay and interaction
<Carousel
items={stories}
autoplay
interval={4500}
pauseOnHover
pauseOnFocus
pauseOnInteraction
loop
/>Autoplay advances only when there are at least two items and interval is greater than zero. The play/pause button appears when autoplay is enabled. Hover, focus, and pointer interaction pause playback according to the corresponding options. The transition also respects prefers-reduced-motion.
draggable enables pointer/touch gestures and is true by default. A horizontal or vertical gesture of approximately 40px navigates one position. Pointer dragging ignores interactive controls such as arrows, dots, and thumbnails, so those controls remain clickable.
Multiple slides and orientation
<Carousel items={products} slidesPerView={3} gap={24} loop={false} />
<Carousel items={updates} orientation="vertical" showDots={false} />orientation="horizontal"uses Left/Right keys and horizontal dragging; default.orientation="vertical"uses Up/Down keys and vertical dragging.slidesPerViewis floored, clamped to at least1, and limited to the number of items.gapaccepts a number in pixels or any CSS length string.- The number of navigable positions is
items.length - visibleSlides + 1(at least one). Dots and status use this number.
Controlled and uncontrolled state
const [index, setIndex] = useState(0);
<Carousel
activeIndex={index}
items={stories}
onChange={(nextIndex, item) => {
setIndex(nextIndex);
analytics.track('carousel-change', { key: item.key });
}}
/>Use activeIndex for controlled state. Without it, defaultActiveIndex initializes the internal position. onChange runs after a valid arrow, dot, thumbnail, keyboard, drag, autoplay, or programmatic prop change navigation. The callback receives the new index and item.
Disabled items receive a disabled visual state and are skipped by navigation when possible:
<Carousel items={[
{ key: 'available', title: 'Available' },
{ key: 'unavailable', title: 'Unavailable', disabled: true },
{ key: 'next', title: 'Next item' },
]} />Custom slide rendering
<Carousel
aria-label="Editorial stories"
items={stories}
variant="minimal"
renderItem={(item, index, state) => (
<article data-active={state.active}>
<span>0{index + 1}</span>
<h3>{item.title}</h3>
<p>{item.description}</p>
<small>{state.visible ? 'Visible' : 'Hidden'}</small>
</article>
)}
/>renderItem replaces the built-in item markup. Its state contains active, the item index, and whether the item is currently visible.
Images and lazy loading
<Carousel
items={photos}
lazy
thumbnailSize={80}
showThumbnails
/>When lazy is true, non-visible images use native loading="lazy"; visible images load eagerly. image is the source URL and alt is its alternative text. For decorative images, provide an empty alt string.
API reference
CarouselItem
| Property | Type | Description |
|---|---|---|
key | string | Required stable identity. |
content | ReactNode | Arbitrary content after the description. |
label | ReactNode | Accessible slide label when it is a string. |
eyebrow | ReactNode | Small label above the title. |
title | ReactNode | Optional heading. |
description | ReactNode | Supporting text. |
badge | ReactNode | Badge content. |
image | string | Image URL. |
alt | string | Alternative text for the image. |
disabled | boolean | Marks the item disabled and causes navigation to skip it when possible. |
CarouselRenderState
| Property | Type | Description |
|---|---|---|
active | boolean | Whether this item is the current starting position. |
index | number | Zero-based item index. |
visible | boolean | Whether the item is currently visible in the viewport. |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | CarouselItem[] | required | Items to display. Empty arrays render nothing. |
activeIndex | number | — | Controlled current starting position. |
defaultActiveIndex | number | 0 | Initial uncontrolled position. |
onChange | (index: number, item: CarouselItem) => void | — | Called after valid navigation. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Track and gesture direction. |
variant | 'default' | 'overlay' | 'minimal' | 'cards' | 'peek' | 'default' | Visual slide treatment. |
effect | 'slide' | 'fade' | 'scale' | 'slide' | Transition effect. |
slidesPerView | number | 1 | Number of visible slides. |
gap | number | string | 16 | Gap between slides; numbers are pixels. |
loop | boolean | true | Wrap navigation at the first and last position. |
autoplay | boolean | false | Automatically advances the carousel. |
interval | number | 5000 | Autoplay delay in milliseconds. |
pauseOnHover | boolean | true | Pause while the viewport is hovered. |
pauseOnFocus | boolean | true | Pause while the viewport has focus. |
pauseOnInteraction | boolean | true | Pause while pointer dragging. |
draggable | boolean | true | Enable pointer/touch dragging. |
showArrows | boolean | true | Render Previous and Next buttons. |
showDots | boolean | true | Render dot navigation. |
showProgress | boolean | false | Render the position progress bar. |
showThumbnails | boolean | false | Render thumbnail tab navigation. |
thumbnailSize | number | 64 | Thumbnail size in pixels. |
transitionDuration | number | 320 | Transition duration in milliseconds. |
previousLabel | string | 'Previous slide' | Accessible label for the Previous button. |
nextLabel | string | 'Next slide' | Accessible label for the Next button. |
playLabel | string | 'Start autoplay' | Accessible label when autoplay is paused. |
pauseLabel | string | 'Pause autoplay' | Accessible label when autoplay is running. |
dotLabel | (index: number, item: CarouselItem) => string | Go to slide N | Accessible label factory for dots and thumbnails. |
lazy | boolean | false | Native lazy-load non-visible images. |
renderItem | (item, index, state) => ReactNode | — | Replace built-in slide markup. |
className | string | — | Additional class on the root section. |
style | CSSProperties | — | Inline styles on the root section. |
aria-label | string | 'Carousel' | Accessible name of the carousel region. |
Public methods
Carousel does not expose a forwarded imperative ref or public methods. Navigation is performed through its rendered controls, keyboard and pointer interactions, autoplay, or controlled activeIndex state. Use onChange to synchronize application state or trigger side effects after navigation.
Accessibility
Give each carousel a meaningful aria-label, especially when a page contains more than one. The component exposes carousel and group semantics, a live position status, labelled arrow controls, selectable dot/thumbnail tabs, keyboard navigation, focus styling, and reduced-motion-friendly transitions. Keep visible labels and alternative text meaningful when slides contain important information.