---
title: "Carousel — React"
canonical: https://sky-ui.cf.sky.com/components/carousel/react
apiPackages: [{"name":"@sky-uk/ui-core","representedVersion":"13.2.0"}]
---

# Carousel — React

An infinite scrolling carousel showcasing visual content.

```js
import { Carousel } from '@sky-uk/ui-core';
```

---

The `Carousel` component provides you with an infinite scrolling carousel.

```tsx
<Carousel $mediaControls $scrollableRegionTabIndex>
  <Carousel.Scrim />
  <Carousel.Controls />

  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide $mediaControls description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

---

## Subcomponents

- `Carousel.Controls` - applies the internal pagination controls that appear on top of the slides.
- `Carousel.Slide` - individual slides allowing the content of each slide to be dictated. Supports the `description` property.
- `Carousel.Scrim` – applies the gradient scrim to the left and right of the `Carousel`. Supports the `$backgroundColor` property to apply a [theme color](/foundations/colours/) to the gradient. Additionally supports the `$opaque` property to force the `$backgroundColor` gradient to end on a solid theme color

---

## Variants

### Basic Carousel

The below example shows the basic `Carousel`, with every optional element turned off.

```tsx
<Carousel $scrollableRegionTabIndex>
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

### Media Carousel

The below example shows the full media `Carousel` with the `$peek` property on.

```tsx
<Carousel $peek $mediaControls>

  <Carousel.Scrim />
  <Carousel.Controls />

  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

---

## Props

### $peek

`$peek` is a boolean property which when set shows the next and previous slide including a scrim overlay.

```tsx
<Carousel $peek>
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

### $scrollableRegionTabIndex

`$scrollableRegionTabIndex` is a boolean property which when set on the `Carousel` component will allow the user to tab into the `Carousel`.
Note: this prop should only be used if the rail has non-interactive content.

```tsx
<Carousel $scrollableRegionTabIndex>
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

### $mediaControls

`$mediaControls` is a boolean property which when set on the `Carousel` component renders media controls on all child slides. `$mediaControls` can also be set on individual slides if the controls are only required on particular slides. `$mediaControls` set on the `Carousel` will take precedence.

#### autoplay / muted

The `Carousel` accepts two additional properties related to the `$mediaControls`.

- `autoplay` is a boolean property that sets the state of the `autoplay` button, this is `false` by default.
- `muted` is a boolean property that sets the state of the `muted` button, this is `true` by default.

```tsx
<Carousel $mediaControls autoplay={true} muted={false}>
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
   </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

---

## Callbacks

### onMount

The `onMount` callback will return an `array` containing `ref`s to all the slides, including the duplicates, in the `Carousel` track. This can be used to manage the state of the slides.

```tsx
() => {

  function Example(){
    const [allSlides, setAllSlides] = React.useState([]);

    const handleMount = e => {
      setAllSlides(e);
    }

    return (
      <Flex $flexDirection="column" $gap={5}>
        <Flex $flexDirection="row" $gap={4}>
          <Text>All slides:</Text>
          <ul>
            {
              allSlides.map((slide, idx) => (
                <Text as="li" key={idx}>
                  <code>{(slide.ref && slide.ref.current) && slide.ref.current.className}</code>
                </Text>
              ))
            }
          </ul>
        </Flex>
        <Carousel $mediaControls onMount={handleMount}>
          <Carousel.Slide description="description of slide 1 content">
            <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 2 content">
            <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 3 content">
            <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>
        </Carousel>
      </Flex>
    );
  }

  return <Example />

}
```

### onSlideChange

The `onSlideChange` callback will return the state of slide slides as an `object` when the transition to the new current slide is complete. The `current` key is the 0 indexed current slide as a `number`. The `ref` key is a `ref` to the current slide.

```tsx
() => {

  function Example(){
    const [slideState, setSlideState] = React.useState({
      current: null
    });

    const handleSlideChange = e => {
      setSlideState(e);
    };

    return (
      <Flex $flexDirection="column" $gap={5}>
        <Text>Current slide: {slideState.current}</Text>
        <Carousel $mediaControls onSlideChange={handleSlideChange}>
          <Carousel.Slide description="description of slide 1 content">
            <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 2 content">
            <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 3 content">
            <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
          </Carousel.Slide>
        </Carousel>
      </Flex>
    );
  }

  return <Example />

}
```

### onMediaControlChange

The `onMediaControlChange` callback will return the state of the media controls as an `object` when any of the media controls change. Management of the media's properties is the concern of the consumer.

```tsx
() => {

  function Example(){
    const [mediaControls, setMediaControls] = React.useState({
      autoplay: false,
      muted: true
    });
    const [allSlides, setAllSlides] = React.useState([]);
    const [currentSlide, setCurrentSlide] = React.useState({
      current: 0,
      ref: undefined
    });

    const handleMount = e => {
      setAllSlides(e);
    }

    const getSlideVideo = slide => {
      const slideRefCurrent = slide.ref.current;

      if (!slideRefCurrent) return null;

      const video = slideRefCurrent.firstChild
      return video.nodeName === 'VIDEO' ? video : null;
    }

    const handleAllSlides = () => {
      const slides = allSlides.filter((slide) => slide.ref.current !== currentSlide.ref.current);

      if (!slides.length) return;

      slides.map(slide => {
        if (!slide.ref) return;

        const video = getSlideVideo(slide);
        video.currentTime = 0;
        video.pause();

        return;
      });
    }

    const handleCurrentSlide = () => {
      if (!currentSlide.ref) return;

      const video = getSlideVideo(currentSlide);

      if(!video) return;

      if (mediaControls.autoplay) {
        video.currentTime = 0;
        video.play();
        return;
      }

      video.pause();
    }

    const handleMediaControlChange = e => {
      setMediaControls(e);
    };

    const handleSlideChange = e => {
      setCurrentSlide(e);
    };

    React.useEffect(() => {
      handleCurrentSlide()
    }, [mediaControls, currentSlide]);

    return (
      <Flex $flexDirection="column" $gap={5}>
        <Flex $flexDirection="column">
          <Text>Media controls</Text>
          <Text>autoplay: {String(mediaControls.autoplay)}</Text>
          <Text>muted: {String(mediaControls.muted)}</Text>
        </Flex>

        <Carousel
          $mediaControls
          onMediaControlChange={handleMediaControlChange}
          onSlideChange={handleSlideChange}
          onMount={handleMount}
        >
          <Carousel.Slide description="description of slide 1 content">
            <Video
              src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4"
              $width="100%"
              $aspectRatio="16 / 9" $height="100%" $width="100%"
              playsInline
            />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 2 content">
            <Video
              src="https://test-videos.co.uk/vids/jellyfish/mp4/h264/360/Jellyfish_360_10s_1MB.mp4"
              $width="100%"
              $aspectRatio="16 / 9" $height="100%" $width="100%"
              playsInline
            />
          </Carousel.Slide>

          <Carousel.Slide description="description of slide 3 content">
            <Video
              src="https://test-videos.co.uk/vids/sintel/mp4/h264/720/Sintel_720_10s_1MB.mp4"
              $width="100%"
              $aspectRatio="16 / 9" $height="100%" $width="100%"
              playsInline
            />
          </Carousel.Slide>
        </Carousel>
      </Flex>
    );
  }

  return <Example />

}
```

## Functions

The `Carousel` supports functions to programmatically scroll or snap to slides.
Use the `ref` prop to get a reference to the `Carousel` component and call the function on the reference.

| Function | Props | Description |
| --- | --- | --- |
| `moveNext` |  | Scrolls the Carousel to the next slide |
| `movePrevious` |  | Scrolls the Carousel to the previous slide |
| `moveTo` | `number` | Scrolls the Carousel to the slide at the provided value |
| `snapTo` | `number` | Snaps the Carousel to the slide at the provided value |

```tsx
function Example() {
  const carouselRef = React.useRef(null);

  const [snapToValue, setSnapToValue] = React.useState(0);
  const [moveToValue, setMoveToValue] = React.useState(0);

  const handleNext = () => {
    if (carouselRef.current) {
      carouselRef.current.moveNext();
    }
  };

  const handlePrevious = () => {
    if (carouselRef.current) {
      carouselRef.current.movePrevious();
    }
  };

  const handleSnapTo = () => {
    carouselRef.current.snapTo(snapToValue);
  };

  const handleMoveTo = () => {
    carouselRef.current.moveTo(moveToValue);
  };

  return (
    <Flex $flexDirection="column" $gap={5}>
      <Flex $flexDirection="column" $gap={5}>
          <Flex $gap={4} $justifyContent="space-between" $alignItems="center">
            <Button onClick={handlePrevious}>Previous</Button>
            <Button onClick={handleNext}>Next</Button>
          </Flex>
          <Flex
            $gap={4}
            $flexDirection={{ xs: 'column', md: 'row' }}
            $justifyContent="space-between"
            $alignItems="center"
          >
            <InputGroup
              inputProps={{
                value: snapToValue,
                onChange: e => setSnapToValue(Number(e.target.value))
              }}
              buttonProps={{
                text: 'snapTo',
                onClick: handleSnapTo
              }}
            />
            <InputGroup
              inputProps={{
                value: moveToValue,
                onChange: e => setMoveToValue(Number(e.target.value))
              }}
              buttonProps={{
                text: 'moveTo',
                onClick: handleMoveTo
              }}
            />
          </Flex>
        </Flex>
      <Carousel ref={carouselRef}>
        <Carousel.Slide description="description of slide 1 content">
          <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
        </Carousel.Slide>

        <Carousel.Slide description="description of slide 2 content">
          <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
        </Carousel.Slide>

        <Carousel.Slide description="description of slide 3 content">
          <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
        </Carousel.Slide>
      </Carousel>
    </Flex>
  );
}
```

---

## Light variant

Use `$appearance` to alternate between `default` and `light` to provide contrast on shaded backgrounds.

```tsx
<Carousel $appearance="light">
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

---

## Accessibility

The `Carousel` will automatically handle accessibility inline with the w3c recommendation. You will need to pass your own unique `aria-label` to the component to fully comply. [(See W3C)](https://www.w3.org/WAI/tutorials/carousels/).

```jsx
<Carousel aria-label="trending shows">
  <Carousel.Slide description="description of slide 1 content">
    <Image src="https://placehold.co/1280x720" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 2 content">
    <Image src="https://placehold.co/1366x768" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>

  <Carousel.Slide description="description of slide 3 content">
    <Image src="https://placehold.co/1600x900" $aspectRatio="16 / 9" $height="100%" $width="100%" />
  </Carousel.Slide>
</Carousel>
```

---

## System Modifiers

The `Carousel` component also accepts props applied using the following system modifiers:

- [margin](/core/system/margin/)

---

## Translatable Fields

The `Carousel` support translation on the following fields:

- `carousel.roleDescription` - controls the roleDescription. [(See MDN)](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-roledescription)
- `carousel.paginationDivider.label` - controls the descriptive text for the pagination divider
- `carousel.previous.label` - controls the descriptive text for the previous slide action
- `carousel.next.label` - controls the descriptive text for the next slide action

For more on translatable fields, view the `useTranslation` docs here: [useTranslation](/core/hooks/use-translation/)
