Carousel
A browser-enhanced scroll carousel with native region and slide semantics, controls, vertical layout, responsive sizing, status hooks, and app-owned autoplay composition.
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from 'radcn/carousel'Live package example
Render Demo, Size, Spacing, Orientation, API, and Plugin-style examples with Card slides, public events, and app-owned behavior.
Example Parity
Render Demo, Size, Spacing, Orientation, API, and Plugin-style examples with Card slides, public events, and app-owned behavior.
import { Card, CardContent } from 'radcn/card'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from 'radcn/carousel'
const slides = ['1', '2', '3', '4', '5']
function SlideCard({ label }: { label: string }) {
return (
<Card class="radcn-carousel-slide-card">
<CardContent style="display:grid;min-height:inherit;place-items:center;padding:1rem;">
<span>{label}</span>
</CardContent>
</Card>
)
}
function SlideItems() {
return slides.map((slide, index) => (
<CarouselItem ariaLabel={`Slide ${index + 1} of ${slides.length}`} index={index} selected={index === 0}>
<SlideCard label={slide} />
</CarouselItem>
))
}
export function CarouselPreview() {
return (
<div class="carousel-preview">
<Carousel class="radcn-carousel--demo" ariaLabel="Featured slides">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div data-carousel-example>
<Carousel class="radcn-carousel--api" ariaLabel="API slides">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div class="radcn-carousel-status" data-carousel-status>Slide 1 of 5</div>
</div>
<Carousel class="radcn-carousel--size" ariaLabel="Responsive slides">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel class="radcn-carousel--spacing" ariaLabel="Compact slides">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<Carousel class="radcn-carousel--orientation" ariaLabel="Vertical slides" orientation="vertical">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div data-carousel-autoplay data-carousel-delay="2000">
<Carousel class="radcn-carousel--plugin" ariaLabel="Autoplay slides">
<CarouselContent><SlideItems /></CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div class="radcn-carousel-plugin-note">Autoplay pauses on hover.</div>
</div>
</div>
)
}
export function enhanceCarouselStatus(root: ParentNode = document) {
root.querySelectorAll<HTMLElement>('[data-carousel-example]').forEach((example) => {
let carousel = example.querySelector<HTMLElement>('[data-radcn-carousel]')
let status = example.querySelector<HTMLElement>('[data-carousel-status]')
if (!carousel || !status) return
let sync = () => {
status.textContent = 'Slide ' + (carousel.dataset.current || '1') + ' of ' + (carousel.dataset.count || '0')
}
carousel.addEventListener('radcn-carousel-select', sync)
carousel.addEventListener('radcn-carousel-scroll', sync)
sync()
})
}
export function enhanceCarouselAutoplay(root: ParentNode = document) {
root.querySelectorAll<HTMLElement>('[data-carousel-autoplay]').forEach((example) => {
let next = example.querySelector<HTMLButtonElement>('[data-radcn-carousel-next]')
if (!next) return
let delay = Number(example.dataset.carouselDelay || '2000')
let timer = window.setInterval(() => next.click(), delay)
example.addEventListener('mouseenter', () => window.clearInterval(timer))
example.addEventListener('mouseleave', () => {
timer = window.setInterval(() => next.click(), delay)
})
})
}Installation
Intended future install command. RadCN is private and not published to npm yet, so this snippet documents the target user-facing API rather than something external consumers can run today.
pnpm add radcn # intended future package
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from 'radcn/carousel'Theming
RadCN tokens read the resolved theme from the document. Store the user's preference separately, then resolve system preferences to a concrete light or dark theme before setting package tokens.
<html data-radcn-theme-mode="system" data-radcn-theme="dark">
...
</html>Accessibility
- Carousel renders a named region with aria-roledescription="carousel" and each item renders a slide group.
- CarouselItem can provide ariaLabel or ariaLabelledby so numbered Card slides have deterministic accessible names.
- Previous and Next are native buttons with stable accessible labels and disabled boundary states.
- Enhanced carousels expose current/count data hooks and selection events so app-owned status text can remain visible and synchronized.
- Keyboard movement uses the active axis: left/right for horizontal, up/down for vertical, plus Home and End.
Customization
- Use class, style, and CSS variables such as --radcn-carousel-width, --radcn-carousel-gap, --radcn-carousel-item-size, and --radcn-carousel-vertical-height to tune layout.
- Card slides are ordinary composition; Carousel does not require or own Card.
- Responsive size and spacing examples map to public classes and CSS variables instead of Tailwind basis or negative-margin utilities.
- Autoplay/plugin behavior can be app-owned browser enhancement over public controls and data hooks.
Remix 3 Notes
- React state/effects/context and shadcn setApi map to public data hooks, radcn-carousel-select events, radcn-carousel-scroll events, and app-owned browser state.
- Embla, useEmblaCarousel, opts, plugins, and embla-carousel-autoplay are upstream implementation mechanics, not RadCN dependencies.
- Lucide arrows are presentation choices; RadCN controls use package-owned glyphs or app-owned children with accessible button labels.
- Tailwind width, max-width, aspect-square, padding, negative margin, basis, height, and responsive utilities map to RadCN classes, inline styles, or CSS variables.
