Components
Carousel
BetaHorizontally scrolling content slider with previous/next arrow buttons and dot indicators. Used for destination cards, hotel photos, and offer showcases.
Purpose
The Carousel displays a horizontal sequence of cards or items when showing all of them simultaneously would require too much vertical space or visual density. It is used for:
- Destination listing carousels — featured destinations on the homepage
- Hotel gallery strips — multiple room or property photos
- Offer rows — "Deals of the week" on the offers page
- Related holidays — "You might also like" at the bottom of a detail page
The Carousel manages its own slide position state. Pass any content via renderItem — the Carousel handles the scroll track, arrows, and dot indicators. On the last slide group, the next arrow disables. On the first, the previous arrow disables.
Usage
Reach for Carousel when showing every item at once would take too much vertical space or visual density — a homepage destinations row, a hotel photo strip, or a "you might also like" module. When the content is purely a set of images meant to be viewed one at a time (e.g. a hotel's full photo set on its detail page), prefer ImageGallery, which is optimised for that single-purpose viewing pattern.
Carousels typically sit within a titled content section on a landing or detail page, scrolling horizontally within the page's normal vertical flow.
Anatomy
- 1Slide track — Horizontal flex container. Clip overflow, translate-x to slide. Transition 300ms ease-out.
- 2Slide item — Each item wrapped in a role="group" div with aria-roledescription="slide" and aria-label="Slide N of M".
- 3Previous button — Circular outline button with ChevronLeft. Disabled on first slide.
- 4Dot indicators — Pill-shaped dots — active dot is wider (w-6) in teal, inactive dots are circles (w-2) in grey.
- 5Next button — Circular outline button with ChevronRight. Disabled on last slide group.
Variants
Multiple slides visible
Use slidesPerView to show more than one slide at a time.
Without dots
Use showDots={false} for compact carousels where arrows alone are sufficient navigation.
Without arrows
For touch-first carousels or when the scroll behaviour provides its own affordance.
States
| Element | State | Appearance |
|---|---|---|
| Previous button | Default | White bg, grey border |
| Previous button | Hover | Teal border, teal text |
| Previous button | Disabled (first slide) | 40% opacity |
| Next button | Default | White bg, grey border |
| Next button | Disabled (last group) | 40% opacity |
| Dot | Inactive | w-2 grey-300 circle |
| Dot | Active | w-6 teal pill |
| Arrows | Focus | Teal focus ring |
| Dots | Focus | Teal focus ring |
Best practices
Match slidesPerView to the number of cards that fit naturally at the target breakpoint.
slidesPerView={3} with gap={24} shows a grid-like layout for destination cards.
Always use slidesPerView=1 regardless of viewport width — adapt the value per breakpoint using CSS or a responsive wrapper.
slidesPerView={1} on a desktop-wide carousel wastes screen space and increases click count.
Always pass ariaLabel to describe the carousel's content to assistive technology.
ariaLabel='Featured destinations' gives screen reader users context for the region.
Omit the ariaLabel prop.
An unlabelled carousel region gives screen readers no context for what is being displayed.
Place properly structured card components inside renderItem — links, headings, and images should be complete within each slide.
Cards inside a Carousel should have their own heading, image, and link for accessibility.
Place non-interactive decorative images as the only content inside slides.
Icon-only slides leave screen reader users without content to interact with.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items* | T[] | — | Array of data items to render. Each item is passed to renderItem. |
| renderItem* | (item: T, index: number) => ReactNode | — | Render function called for each item. Return a slide's content. |
| slidesPerView | number | 1 | Number of slides visible at once. Determines slide width as a fraction of the container. |
| gap | number | 16 | Gap between slides in pixels. |
| showDots | boolean | true | Whether to show dot indicators below the carousel. |
| showArrows | boolean | true | Whether to show the previous/next arrow buttons. |
| ariaLabel | string | 'Carousel' | Accessible label for the carousel region. |
| className | string | — | Additional Tailwind classes on the root container. |
Accessibility
ARIA roles
<div role="region" aria-label="Featured destinations" aria-roledescription="carousel">
<div role="group" aria-roledescription="slide" aria-label="Slide 1 of 6">
{/* slide content */}
</div>
</div>
role="region"witharia-labelmakes the carousel a named landmark- Each slide has
role="group"andaria-roledescription="slide"per the ARIA carousel pattern aria-label="Slide N of M"on each slide informs screen reader users of position
Hidden slides
Slides outside the visible window have aria-hidden={true} so screen readers don't announce off-screen content.
Keyboard interaction
| Key | Action |
|---|---|
Tab | Navigate to previous button, dot indicators, next button |
Enter / Space | Activate a dot or arrow button |
| Arrow keys | Not implemented on the carousel itself — use Tab to navigate controls |
Related components
- ImageGallery — use instead when the content is a single-purpose photo set rather than mixed card content.