
Article 1
Article description
Our Carousel component supports the following features:
embla-carousel-react library to handle the carousel logic. The <CarouselRoot /> component accepts all options that the Embla library accepts.







Navigation buttons positioning
For sites that must comply with WCAG AA standards, the navigation buttons must be positioned above the carousel, and must appear before the carousel in the DOM. This is so that screen reader users can navigate to a slide and then view that slide, without having to navigate backwards.
<CarouselRoot aria-label="An example carousel with multiple image slides">
<PageWidth width="skinny" className="mb-2 flex justify-end gap-2">
<CarouselPrevButton />
<CarouselNextButton />
</PageWidth>
<PageWidth width="skinny">
<CarouselWindow className="rounded-xl">
<Carousel>
<CarouselTrack>
{images.map((image, index) => (
<CarouselItem
key={index}
className="w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] lg:w-[calc(33.33%-2.5rem)]"
>
<Image
src={image}
alt={image.alt}
className="aspect-[3/2] rounded-xl object-cover md:aspect-square lg:aspect-[2/3]"
/>
</CarouselItem>
))}
</CarouselTrack>
</Carousel>
</CarouselWindow>
</PageWidth>
</CarouselRoot>Carousel items can be styled with Tailwind classes. You can set each slide to
take up the full carousel width by applying className="w-full" to the
<CarouselItem /> component.








<PageWidth width="skinny">
<CarouselRoot aria-label="An example carousel with single full width image slides">
<div className="mb-2 flex justify-end gap-2">
<CarouselPrevButton />
<CarouselNextButton />
</div>
<CarouselWindow className="rounded-xl">
<Carousel>
<CarouselTrack>
{images.map((image, index) => (
<CarouselItem key={index} className="w-full">
<Image
src={image}
alt={image.alt}
className="aspect-[3/2] rounded-xl object-cover md:aspect-[5/2]"
/>
</CarouselItem>
))}
</CarouselTrack>
</Carousel>
</CarouselWindow>
</CarouselRoot>
</PageWidth>Faded edges can be added to the left and right of the carousel.
Important: The two <CarouselFadedEdge /> components must be wrapped in a
div with className="relative" to work correctly.








<CarouselRoot aria-label="An example carousel with faded edges">
<PageWidth width="skinny" className="mb-2 flex justify-end gap-2">
<CarouselPrevButton />
<CarouselNextButton />
</PageWidth>
<CarouselWindow>
<PageWidth width="skinny" className="relative">
<CarouselFadedEdge side="left" />
<CarouselFadedEdge side="right" />
<Carousel>
<CarouselTrack>
{images.map((image, index) => (
<CarouselItem
key={index}
className="w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] lg:w-[calc(33.33%-2.5rem)]"
>
<Image
src={image}
alt={image.alt}
className="aspect-[3/2] rounded-xl object-cover md:aspect-square lg:aspect-[2/3]"
/>
</CarouselItem>
))}
</CarouselTrack>
</Carousel>
</PageWidth>
</CarouselWindow>
</CarouselRoot>Carousel items are agnostic to their content. You may use the carousel to render a list of articles.
<CarouselRoot aria-label="An example carousel with multiple article slides">
<PageWidth width="skinny" className="mb-2 flex justify-end gap-2">
<CarouselPrevButton />
<CarouselNextButton />
</PageWidth>
<PageWidth width="skinny">
<CarouselWindow className="rounded-xl">
<Carousel>
<CarouselTrack>
{articles.map((article) => (
<CarouselItem key={article.id} className="w-80">
<Article {...article} />
</CarouselItem>
))}
</CarouselTrack>
</Carousel>
</CarouselWindow>
</PageWidth>
</CarouselRoot>In this example, each slide is a video group. The slide component uses the
useCarouselItemContext() hook to access the context of the carousel item. It
then uses that information to apply an animated styling to the inactive slides
and to play/pause the video as the slides become active/inactive.
const VideoSlide = ({ video }: { video: Video }) => {
const { isActive, slideIndex, activeSlideIndex } = useCarouselItemContext();
const { ref, play, pause, isPlaying } = useVideoPlayer();
useEffect(() => {
if (isActive) {
play();
} else {
pause();
}
}, [isActive, play, pause]);
return (
<div
className={cn(
'relative transition-all duration-300',
slideIndex < activeSlideIndex && 'origin-right scale-90 opacity-50',
slideIndex > activeSlideIndex && 'origin-left scale-90 opacity-50',
)}
>
<Video ref={ref} src={video.src} />
<button onClick={isPlaying ? pause : play}>
{isPlaying ? (
<RiPauseLine className="h-4 w-4" />
) : (
<RiPlayFill className="h-4 w-4" />
)}
</button>
</div>
);
};
const VideoCarousel = () => {
return (
<CarouselRoot aria-label="An example carousel with cool animated video slides">
<PageWidth width="skinny" className="mb-2 flex justify-end gap-2">
<CarouselPrevButton />
<CarouselNextButton />
</PageWidth>
<CarouselWindow>
<PageWidth width="skinny" className="relative">
<CarouselFadedEdge side="left" />
<CarouselFadedEdge side="right" />
<Carousel>
<CarouselTrack>
{videos.map((video, index) => (
<CarouselItem key={index} className="w-full">
<VideoSlide video={video} />
</CarouselItem>
))}
</CarouselTrack>
</Carousel>
</PageWidth>
</CarouselWindow>
</CarouselRoot>
);
};The following libraries need to be installed:
| 3rd Party Libraries | version |
|---|---|
embla-carousel-react | 9.0.0-rc01 |
The following folders need to be copied to your project:
| Copy to project | Reason |
|---|---|
ui/components/carousel/ | Carousel components and hooks |
ui/structure/page-width/ | Useful for setting the width bounds of the carousel |
<CarouselRoot />Required at the top level of the carousel. It provides all the context and logic for the carousel.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'section'> | All props from the <section> element are accepted. |
options | EmblaOptions | Used to configure the underlying Embla carousel instance. |
plugins | EmblaPlugin[] | Add plugins to the underlying Embla carousel instance. |
controller | CarouselController | If you want to use a custom carousel controller, you can pass it here. This is in case you need access to the carousel state outside of the <CarouselRoot /> component. |
<CarouselWindow />An overflow-hidden container that contains** **the carousel. In the standard example
this sits inside a <PageWidth /> component so that the visual boundary of the
carousel is confined to the page width.
If however, parts of the non-active slides are visible outside of the page width
(see faded edges example), the <CarouselWindow /> should be expanded to allow
for the additional width.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'div'> | All props from the <div> element are accepted. |
<Carousel />An aria-live container that contains the carousel. Slides that are outside the boundary of this component are considered "not visible".
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'section'> | All props from the <div> element are accepted. |
<CarouselTrack />The track element that slides left to right.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'div'> | All props from the <div> element are accepted. |
<CarouselItem />An individual slide container. When outside the boundaries of the <Carousel />
component, the slide is considered "not visible" and given an inert attribute
so that it becomes non-interactive and is hidden from screen readers.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'div'> | All props from the <div> element are accepted. |
| Data attribute | Type | Description |
|---|---|---|
data-slide-index | number | The index of the slide. |
data-visible | boolean | Whether the slide is visible. |
data-active | boolean | Whether the slide is active. |
<CarouselPrevButton />The previous navigation button.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'button'> | All props from the <button> element are accepted. |
<CarouselNextButton />The next navigation button.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'button'> | All props from the <button> element are accepted. |
<CarouselFadedEdge />An empty div that adds a nice faded efect to the edge of the carousel. It must
be added inside an element with poosition: relative.
| Prop | Type | Description |
|---|---|---|
| @extends | ComponentProps<'div'> | All props from the <div> element are accepted. |
side | 'left' | 'right' | The side of the edge to apply the faded effect to. |
useCarouselContext()Call from inside the <CarouselRoot /> component. Returns the context of the
carousel.
| Property | Type | Description |
|---|---|---|
controller | CarouselController | The controller of the carousel. |
useCarouselItemContext()Call from inside an individual Carousel Item. Returns the context of the carousel item.
| Property | Type | Description |
|---|---|---|
carouselId | string | The id of the carousel. |
slideIndex | number | The index of the current slide. |
isVisible | boolean | Whether the slide is visible. |
isActive | boolean | Whether the slide is active. |
activeSlideIndex | number | The index of the active slide. |