Carousel - React
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 thedescriptionproperty.Carousel.Scrim– applies the gradient scrim to the left and right of theCarousel. Supports the$backgroundColorproperty to apply a theme color to the gradient. Additionally supports the$opaqueproperty to force the$backgroundColorgradient to end on a solid theme color
Variants
Basic Carousel
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>
Media 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.
autoplayis a boolean property that sets the state of theautoplaybutton, this isfalseby default.mutedis a boolean property that sets the state of themutedbutton, this istrueby 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.
| 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 |
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 dividercarousel.previous.label- controls the descriptive text for the previous slide actioncarousel.next.label- controls the descriptive text for the next slide action
For more on translatable fields, view the useTranslation docs here: useTranslation