React Aria Carousel

The carousel for the modern age.


React Aria Carousel is a collection of React hooks and components that provide the behavior and accessibility implementation for building a carousel. Unlike many other carousels, this implementation uses the latest browser and DOM APIs for scrolling and snapping.

Features

  • Native browser scroll-snapping and smooth-scrolling for performant transitions across pointer types.
  • Comprehensive behavior and accessibility coverage for all elements of a carousel, including pagination, infinite scrolling, autoplay, and more.
  • Support for showing one or many items per page, implemented with responsive-design.
  • Support for vertical and horizontal scrolling.
  • Support for infinite scrolling.
  • Support for autoplay with affordances for disabling it.
  • Support for mouse dragging.
  • Written in TypeScript.

Installation

npm install react-aria-carousel

Usage

Anatomy

import {
  Carousel,
  CarouselButton,
  CarouselButton,
  CarouselItem,
  CarouselScroller,
  CarouselTab,
  CarouselTabs,
} from "react-aria-carousel";
 
<Carousel>
  <CarouselAutoplayControl />
  <CarouselButton dir="prev" />
  <CarouselButton dir="next" />
  <CarouselScroller>
    <CarouselItem />
  </CarouselScroller>
  <CarouselTabs>
    {(page) => (
      <CarouselTab index={page.index} />
    )}
  </CarouselTabs>
</Carousel>

Carousels have a root element denoting the carousel region, which contains all of the different parts of the carousel, including the scrolling container, next and previous buttons, and optionally, tabs and the autoplay toggle.

Below is a simple example with minimal styles to show all of the different parts working together.

Multiple Items

The number of items present on a page can be changed with the itemsPerPage prop. You can change the spacing between items with the spaceBetweenItems prop.

Looping

By default, the carousel will not loop if one scrolls to either end. You can force the carousel to "wrap" in two different ways.

If the loop prop is set to "infinite", the carousel will give the appearance of seamlessly wrapping to the opposite end.

If the loop props is set to "native", the user still won't be able to scroll to the other end. However, the next and previous buttons and keyboard controls will scroll the user to the other end. ("Native" in this case means that the behavior of the scroll container aren't manipulated, it just provides the functionality to scroll the user to the other end.)

Orientation

By default, the carousel will scroll horizontally. This can be changed to scroll vertically with the orientation prop.

Mouse Dragging

The carousel uses CSS scroll snap to position slides at various snap positions. This allows users to scroll through the slides very naturally, especially on touch devices. Unfortunately, desktop users won’t be able to click and drag with a mouse, which can feel unnatural. The carousel provides this functionality with the mouseDragging prop.

This example is best demonstrated using a mouse. Try clicking and dragging the slide to move it.

Autoplay

Use the autoplay prop to automatically progress the carousel, dictated by the autoplayInterval prop. This functionality works great with the loop prop enabled. For best usability and accessibility, autoplay will pause if the user starts interacting with the carousel, and will resume when the stop interacting with it. Autoplay will also be disabled if the user's operating system or browser settings has the "prefers reduced motion" option enabled.

It's very important to include the CarouselAutoplayControl component when using this prop! Users must be able to disable this functionality.

Scroll Hints

The carousel has two different ways to hint to users that there are more items in the carousel.

The scrollPadding prop simply applies CSS scroll-padding to the scroll container. This has the effect of hinting the presence of items on either side of the carousel.

You can also manipulate the itemsPerPage prop to hint at more items. If you use a non-integer number, like 2.25, the carousel will show a quarter of the following item in the list. This option has the benefit of keeping the edges of the carousel nicely aligned to its container (as opposed to scrollPadding).

Scroll Padding

Fractional Items per Page

Accessibility & Labeling

React Aria Carousel includes most required labels and HTML semantics by default, but requires a label (via aria-label or aria-labelledby) for the Carousel component for it to properly be described by screen readers.

All other aria attributes and descriptions are provided in English. For non-English languages, they can be overridden. Here's an example of the component translated to Italian:

Note: translations may be innaccurate, I used Google Translate for them :).

Hooks

If you need to customize things even further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API.

import {
  useCarousel,
  useCarouselItem,
  useCarouselTab,
} from "react-aria-carousel";
 
export function Carousel() {
  const [assignScrollerRef, carousel] = useCarousel({
    spaceBetweenItems: "16px",
    itemsPerPage: 2,
  });
 
  const {
    rootProps,
    prevButtonProps,
    nextButtonProps,
    scrollerProps,
    tabProps,
    pages,
    autoplayControlProps,
  } = carousel;
 
  return (
    <div {...rootProps}>
      <button {...autoplayControlProps}>Toggle autoplay</button>
      <div>
        <button {...prevButtonProps}>Previous</button>
        <button {...nextButtonProps}>Next</button>
      </div>
      <div {...scrollerProps} ref={assignScrollerRef}>
        <CarouselItem index={0} state={carousel} />
      </div>
      <div {...tabProps}>
        {pages.map((_, i) => (
          <CarouselTab key={i} index={i} state={carousel} />
        ))}
      </div>
    </div>
  );
}
 
function CarouselItem({ index, state }) {
  const { itemProps } = useCarouselItem({ index }, state);
  return <div {...itemProps} />;
}
 
function CarouselTab({ index, state }) {
  const { tabProps } = useCarouselTab({ index }, state);
  return <div {...tabProps} />;
}

API

The component accepts any valid HTML attributes, in addition to the following:

NameTypeDefaultDescription
autoplaybooleanfalseIf true, the carousel will scroll automatically when the user is not interacting with
autoplayIntervalnumber5000Specifies the amount of time, in milliseconds, between each automatic scroll.
childrenReactNode-The elements of the carousel.
initialPagesnumber[][][]Define the organization of pages on first render. Useful to render navigation during SSR.
itemsPerPagenumber1Number of items visible in a page. Can be an integer to show each item with equal dimensions, or a floating point number to "peek" subsequent items.
loop'infinite' | 'native' | falsefalseControls the pagination behavior at the beginning and end.
mouseDraggingbooleanfalseControls whether the user can scroll by clicking and dragging with their mouse.
onActivePageIndexChange(args: {index: number}) => void-Handler called when the activePageIndex changes.
orientation'horizontal' | 'vertical''horizontal'The carousel scroll direction
scollBy'page' | 'item''page'Controls whether scrolling snaps and pagination progresses by item or page.
scrollPaddingstring-The amount of padding to apply to the scroll area, allowing adjacent items to become partially visible.
spaceBetweenItemsstring'0px'The gap between items.

CarouselScroller

The component accepts any valid HTML attributes, in addition to the following:

NameTypeDefaultDescription
childrenReactElement | ReactElement[] | ((item: T, index: number) => ReactElement)-The collection of carousel items.
itemsArray<T>-The data with which each item should be derived.

CarouselItem

The component accepts any valid HTML attributes, in addition to the following:

NameTypeDefaultDescription
indexnumber-The placement of the item in the carousel.

CarouselButton

The component accepts any valid HTML button attributes, in addition to the following:

NameTypeDefaultDescription
dir'next' | 'prev'-Direction that the carousel should scroll when clicked.

CarouselTabs

The component accepts any valid HTML attributes, in addition to the following:

NameTypeDefaultDescription
children(props: { isSelected: boolean; index: number }) => ReactNode-Function that returns a CarouselTab

CarouselTab

CarouselTab does not have any unique props, and accepts any valid HTMLButtonElement prop.

CarouselAutoplayControl

The component accepts any valid HTML attributes, in addition to the following:

NameTypeDefaultDescription
childrenReactNode | ((props: {autoplayUserPreference: boolean}) => ReactNode)-The content of the button.

useCarousel

NameTypeDefaultDescription
autoplaybooleanfalseIf true, the carousel will scroll automatically when the user is not interacting with
autoplayIntervalnumber5000Specifies the amount of time, in milliseconds, between each automatic scroll.
initialPagesnumber[][][]Define the organization of pages on first render. Useful to render navigation during SSR.
itemsPerPagenumber1Number of items visible in a page. Can be an integer to show each item with equal dimensions, or a floating point number to "peek" subsequent items.
loop'infinite' | 'native' | falsefalseControls the pagination behavior at the beginning and end.
mouseDraggingbooleanfalseControls whether the user can scroll by clicking and dragging with their mouse.
onActivePageIndexChange(args: {index: number}) => void-Handler called when the activePageIndex changes.
orientation'horizontal' | 'vertical''horizontal'The carousel scroll direction
scollBy'page' | 'item''page'Controls whether scrolling snaps and pagination progresses by item or page.
scrollPaddingstring-The amount of padding to apply to the scroll area, allowing adjacent items to become partially visible.
spaceBetweenItemsstring'0px'The gap between items.

useCarouselItem

NameTypeDefaultDescription
itemItem<T>-An item in the collection of carousel items.

useCarouselTab

NameTypeDefaultDescription
indexnumber-An index of a page in the carousel.
isSelectedboolean-Whether the page is the active, visible page of the carousel.

Browser Support

A goal of this project is to use modern browser technology. While most APIs are widely supported, the scrollend notably is not supported in Safari (as of May 2024). A polyfill is not included to future-proof the project, but you will likely want to include one.

Acknowledgements