Skip to main navigationSkip to main content
Return to Sky UI homepage
import { Carousel } from '@sky-uk/ui-core';

The Carousel component provides you with an infinite scrolling carousel.

<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 to the gradient. Additionally supports the $opaque property to force the $backgroundColor gradient to end on a solid theme color

Variants

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

<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>

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

<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.

<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.

<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.
<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 refs to all the slides, including the duplicates, in the Carousel track. This can be used to manage the state of the slides.

() => {

  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.

() => {

  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.

() => {

  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.

FunctionPropsDescription
moveNextScrolls the Carousel to the next slide
movePreviousScrolls the Carousel to the previous slide
moveTonumberScrolls the Carousel to the slide at the provided value
snapTonumberSnaps the Carousel to the slide at the provided value
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.

<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).

<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:

Translatable Fields

The Carousel support translation on the following fields:

  • carousel.roleDescription - controls the roleDescription. (See MDN)
  • 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