Carousel

A hand-drawn scroll-snap carousel with prev/next controls, built on native scrolling.

Overview

Carousel is a slideshow of horizontally (or vertically) scrolling slides, built on native CSS scroll-snap — no third-party engine. Use it to browse a set of peer items one viewport at a time: a photo gallery, a set of feature cards, an onboarding sequence. Compose it from CarouselContent, CarouselItem, CarouselPrevious, and CarouselNext.

Anatomy

A role="region" labelled as a carousel wrapping a scroll-snap track (CarouselContent) of slides (CarouselItem, each role="group" described as a slide). CarouselPrevious / CarouselNext are Buttons that move one viewport per step and disable at the start / end respectively. Arrow keys scroll along the orientation axis; the scrollbar is visually hidden while snapping stays native.

Variants & States

The only variant axis is orientation ('horizontal' default, or 'vertical'). The control states come from scroll position.

State Status Notes
At start Implemented CarouselPrevious is disabled when the viewport is at the start.
At end Implemented CarouselNext is disabled when the viewport is at the end.
Focus Implemented The region is arrow-key scrollable when focused.
Disabled Not implemented The carousel itself has no disabled state.
Loading Not implemented

Usage

Do

  • Give the carousel (or a wrapper) an explicit width — and, for a vertical carousel, a bounded height on CarouselContent.
  • Provide an aria-label describing the set of slides (e.g. "Photos").
  • Keep one slide per viewport unless you deliberately override flexBasis on CarouselItem.

Don't

  • Don't put essential, time-sensitive content in a carousel — users may never scroll past the first slide.
  • Don't auto-advance without a pause control; motion that can't be stopped is an accessibility problem.

Accessibility

Content

Slides should be peers of roughly equal weight. Keep each slide self-contained — a user might land on any one first — and avoid splitting a single message across slides.

Props API

React@ghds/react

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Scroll axis (Carousel).
...restHTMLAttributes<HTMLDivElement>Native <div> attributes (aria-label, etc.) pass through to the region.
CarouselContentHTMLAttributes<HTMLDivElement>The scroll-snap track; set maxHeight/maxWidth via style to bound it.
CarouselItemHTMLAttributes<HTMLDivElement>A single slide; override flexBasis via style for multiple-per-view.
CarouselPrevious / CarouselNextButtonPropsThe prev/next controls (Button); auto-disable at the ends.

Web Components@ghds/web-components

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Reflected attribute; scroll axis.
slides<gh-carousel-item>Place <gh-carousel-item> slides in the default slot. Import both @ghds/web-components/carousel and /carousel-item.
controlsbuilt-inThe element renders its own prev/next buttons and indicator dots — no separate control elements.

React Native@ghds/react-native

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Scroll axis (Carousel).
accessibilityLabelstringAccessible label for the carousel region.
childrenReactNodeCompose with CarouselContent / CarouselItem / CarouselPrevious / CarouselNext / CarouselIndicators.
testIDstringTest handle for queries.

Code examples

// React — @ghds/react
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselPrevious,
  CarouselNext,
} from '@ghds/react/carousel';

<Carousel orientation="horizontal" aria-label="Photos">
  <CarouselContent>
    <CarouselItem>Slide 1</CarouselItem>
    <CarouselItem>Slide 2</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>;
<!-- Web Components — @ghds/web-components (renders its own prev/next controls + dots) -->
<script type="module">
  import '@ghds/web-components/carousel';
  import '@ghds/web-components/carousel-item';
</script>
<gh-carousel aria-label="Photos">
  <gh-carousel-item>Slide 1</gh-carousel-item>
  <gh-carousel-item>Slide 2</gh-carousel-item>
</gh-carousel>
// React Native — @ghds/react-native
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselPrevious,
  CarouselNext,
  CarouselIndicators,
} from '@ghds/react-native/carousel';

<Carousel accessibilityLabel="Photos">
  <CarouselContent>
    <CarouselItem>{/* slide 1 */}</CarouselItem>
    <CarouselItem>{/* slide 2 */}</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselIndicators />
</Carousel>;

Live Demo

React and Web Components render live below, driven by the same tokens — toggle dark mode in the header to see both respond identically. React Native can’t run in a browser without additional react-native-web wiring (not set up in this site), so its usage is shown as a code sample instead. (The Web Components element renders its own prev/next controls and indicator dots.)

React @ghds/react

Web Components @ghds/web-components

Slide 1
Slide 2
Slide 3
Slide 4
Slide 5