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
Carousel
The component accepts any valid HTML attributes, in addition to the following:
Name | Type | Default | Description |
---|---|---|---|
autoplay | boolean | false | If true, the carousel will scroll automatically when the user is not interacting with |
autoplayInterval | number | 5000 | Specifies the amount of time, in milliseconds, between each automatic scroll. |
children | ReactNode | - | The elements of the carousel. |
initialPages | number[][] | [] | Define the organization of pages on first render. Useful to render navigation during SSR. |
itemsPerPage | number | 1 | Number 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' | false | false | Controls the pagination behavior at the beginning and end. |
mouseDragging | boolean | false | Controls 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. |
scrollPadding | string | - | The amount of padding to apply to the scroll area, allowing adjacent items to become partially visible. |
spaceBetweenItems | string | '0px' | The gap between items. |
CarouselScroller
The component accepts any valid HTML attributes, in addition to the following:
Name | Type | Default | Description |
---|---|---|---|
children | ReactElement | ReactElement[] | ((item: T, index: number) => ReactElement) | - | The collection of carousel items. |
items | Array<T> | - | The data with which each item should be derived. |
CarouselItem
The component accepts any valid HTML attributes, in addition to the following:
Name | Type | Default | Description |
---|---|---|---|
index | number | - | The placement of the item in the carousel. |
CarouselButton
The component accepts any valid HTML button attributes, in addition to the following:
Name | Type | Default | Description |
---|---|---|---|
dir | 'next' | 'prev' | - | Direction that the carousel should scroll when clicked. |
CarouselTabs
The component accepts any valid HTML attributes, in addition to the following:
Name | Type | Default | Description |
---|---|---|---|
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:
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | ((props: {autoplayUserPreference: boolean}) => ReactNode) | - | The content of the button. |
useCarousel
Name | Type | Default | Description |
---|---|---|---|
autoplay | boolean | false | If true, the carousel will scroll automatically when the user is not interacting with |
autoplayInterval | number | 5000 | Specifies the amount of time, in milliseconds, between each automatic scroll. |
initialPages | number[][] | [] | Define the organization of pages on first render. Useful to render navigation during SSR. |
itemsPerPage | number | 1 | Number 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' | false | false | Controls the pagination behavior at the beginning and end. |
mouseDragging | boolean | false | Controls 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. |
scrollPadding | string | - | The amount of padding to apply to the scroll area, allowing adjacent items to become partially visible. |
spaceBetweenItems | string | '0px' | The gap between items. |
useCarouselItem
Name | Type | Default | Description |
---|---|---|---|
item | Item<T> | - | An item in the collection of carousel items. |
useCarouselTab
Name | Type | Default | Description |
---|---|---|---|
index | number | - | An index of a page in the carousel. |
isSelected | boolean | - | 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
- react-snap-carousel is another DOM-first, headless carousel for React that was a big insipiration for this project. It's less opinionated about the layout of the carousel (i.e., does not require CSS grid).
- Shoelace carousel is a Web Component implementation for a DOM-first carousel, that can also be used in React. It's a bit more opinionated about the HTML layout and presentation of the carousel.
- Blog posts and resources used to inform the accessibility implementation:
- "A Step-By-Step Guide To Building Accessible Carousels" by Sonja Weckenmann at Smashing Magazine
- W3C Carousels
- W3C ARIA Carousel Authoring Practices
- Thinking on ways to solve carousels by Adam Argyle (video)