DisplayReact
Slides in a row that scrolls, with buttons to the previous and next page, a tab per page and an optional rotation.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<Carousel.Root>
<Carousel.Value />
<Carousel.Previous />
<Carousel.Next />
<Carousel.Viewport>
<Carousel.Slide />
</Carousel.Viewport>
<Carousel.TabList>
<Carousel.Tab />
</Carousel.TabList>
</Carousel.Root>
// Not in this example: Carousel.AutoplayControlExamples
Multiple
Eight slides three to a view with the fourth peeking: Previous and Next move by three, and there are three tabs, the last one "Slides 7 to 8".
Autoplay
autoplay with loop, the AutoplayControl first in the bar, and a note that the rotation starts stopped under reduced motion.
Thumbnails
A Tab per slide with a picture and the slide's title, which names it.
Vertical
orientation vertical: the slides scroll up and down, and the tabs sit between Previous and Next in a column.
Loop
loop on three slides: Previous on the first goes to the last and Next on the last to the first.
Controlled
value in the example's state: onValueChange writes it and its reason, and First and Last outside the carousel set it.
Cards
Six linked cards two to a view with offscreenSlides="interactive" and mouseDrag.
API reference
34 props across 9 parts, in React syntax. className, style, render are listed once, under Shared props.
AutoplayControlCarousel.AutoplayControl
Starts and stops the rotation; named by what pressing it does. Not rendered without autoplay on the Root. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-playing | Present while rotation is on. |
NextCarousel.Next
Goes to the next page. It is aria-disabled, not disabled, on the last page without loop, so it keeps focus. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
disabledWhether the control should ignore user interaction. | boolean | false |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-disabled | Present when the control is disabled. |
PreviousCarousel.Previous
Goes to the previous page, or to the start of the current one when a scroll rested past it. It is aria-disabled, not disabled, on the first page without loop, so it keeps focus. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
disabledWhether the control should ignore user interaction. | boolean | false |
focusableWhenDisabledWhether the control stays focusable when it is disabled, with aria-disabled. | boolean | true |
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-disabled | Present when the control is disabled. |
RootCarousel.Root
Groups all parts of the carousel: a region with the role description "carousel". Renders a <div>.
| Prop | Type | Default |
|---|---|---|
valueThe index of the current slide, 0-based. | number | — |
defaultValueThe index of the slide shown when the component is initially rendered. | number | 0 |
onValueChangeEvent handler called when the current slide changes. | ((value: number, eventDetails: CarouselRootChangeEventDetails) => void) | — |
onValueChangeCompleteEvent handler called when the scroll that brought the value has settled. | ((value: number) => void) | — |
orientationThe axis the slides scroll on. | CarouselOrientation | 'horizontal' |
alignWhere a slide snaps in the Viewport. | CarouselAlign | 'start' |
slidesPerMoveHow far Previous and Next move: 'auto' by a page (the slides in view), or a number of | CarouselSlidesPerMove | 'auto' |
snapTypeThe CSS scroll snap strictness. | CarouselSnapType | 'mandatory' |
loopWhether Previous on the first page goes to the last page and Next on the last to the first. | boolean | false |
autoplayWhether the slides rotate automatically. | boolean | false |
autoplayIntervalThe time each page is shown while rotating, in milliseconds. | number | 5000 |
playingWhether rotation runs. | boolean | — |
defaultPlayingWhether rotation runs when the component is initially rendered, with autoplay. | boolean | true |
onPlayingChangeEvent handler called when rotation starts or stops. | ((playing: boolean, eventDetails: CarouselRootPlayingChangeEventDetails) => void) | — |
mouseDragWhether a mouse drag scrolls the Viewport. | boolean | false |
offscreenSlidesWhether a slide with no part in view is inert. | CarouselOffscreenSlides | 'inert' |
scrollBehaviorHow the component's own scrolls move: always 'instant' under | CarouselScrollBehavior | 'smooth' |
labelsThe texts, in English by default. | Partial<CarouselLabels> | — |
actionsRefA ref to imperative actions: scrollTo(index), next(), previous(), play() and stop(). | React.RefObject<CarouselRootActions | null> | — |
| Data attribute | Description |
|---|---|
data-orientation | The axis the slides scroll on. |
data-scrolling | Present while the Viewport is scrolling. |
data-dragging | Present while a mouse drag is in progress. |
data-playing | Present while rotation is on. |
data-paused | Present while rotation is on but held by hover, a hidden page or a drag. |
data-at-start | Present when the Viewport shows the start of its range. |
data-at-end | Present when the Viewport shows the end of its range. |
SlideCarousel.Slide
A slide: a group named "N of M", or a tab panel when the carousel has a TabList. Its index is its position among the slides. Renders a <div>.
| Data attribute | Description |
|---|---|
data-index | The slide's index among the slides, 0-based. |
data-active | Present when the slide is the current value. |
data-in-view | Present when part of the slide is in view. |
data-orientation | The axis the slides scroll on. |
TabCarousel.Tab
Selects a page and scrolls to it; selected while its page is the current one. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
pagerequiredThe page the tab selects, 0-based. | number | — |
disabledWhether the tab should ignore user interaction. | boolean | false |
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
| Data attribute | Description |
|---|---|
data-index | The page of the tab, 0-based. |
data-active | Present when the tab's page is the current one. |
data-disabled | Present when the tab is disabled. |
TabListCarousel.TabList
Groups the tabs that pick a page, one per page, as one Tab stop: the arrow keys move focus and, with activateOnFocus, select the page. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
activateOnFocusWhether moving focus with the arrow keys also selects the page. | boolean | true |
loopFocusWhether the arrow keys move focus from the last tab to the first and back. | boolean | true |
childrenThe tabs, or a function that renders the tab of each page. | React.ReactNode | ((page: CarouselTabListPage, index: number) => React.ReactNode) | — |
| Data attribute | Description |
|---|---|
data-orientation | The axis the slides scroll on. |
ValueCarousel.Value
The current page as text, "2 of 5". Plain text, not a live region: the Viewport is. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
childrenThe text, or a function of the current slide, the current page and the page count. | React.ReactNode | ((state: CarouselValueState) => React.ReactNode) | — |
ViewportCarousel.Viewport
The scroll container that holds the slides, and the carousel's live region. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
renderBeforeHydrationWhether to render an inline script that scrolls to the initial slide before React hydrates, | boolean | false |
| Data attribute | Description |
|---|---|
data-orientation | The axis the slides scroll on. |
data-scrolling | Present while the Viewport is scrolling. |
data-dragging | Present while a mouse drag is in progress. |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| EnterSpace | Presses Previous or Next; focus stays on the button | Next |
| Tab | Moves from Next into the current slide, never into a slide out of view | Slide |
| Tab | Reaches the tab of the current page, the list's one Tab stop | Tab |
| ArrowLeftArrowRight | Move focus to the previous or next tab and show its page, wrapping at both ends | TabList |
| ArrowLeftArrowRight | Are mirrored in a right-to-left layout | TabList |
| ArrowUpArrowDown | Move between the tabs when orientation is vertical | TabList |
| HomeEnd | Show the first or the last page | TabList |
| EnterSpace | Stops or starts the rotation; the button's name switches | AutoplayControl |
| Tab | Keyboard focus inside stops the rotation; leaving does not restart it | Root |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="region" with the role description "carousel", named by aria-label or aria-labelledby (a development warning without one); role="group" from the app replaces it | the carousel is a landmark with its name, said as a carousel |
| Slide | role="group" with the role description "slide" and the name "3 of 5"; with a TabList, role="tabpanel" without a role description and the same name | each slide says where it is; the tab panels are not given a second role |
| Slide | inert on a slide with no part in view, decided when a scroll settles and never on a slide that holds focus | Tab, find and the reading order reach only what is shown |
| Viewport | aria-live="off" while rotating and polite otherwise, aria-busy="true" while scrolling, an id the controls point to | a slide the reader brings in is announced once, and rotation is not read out every few seconds |
| Previous | a button named "Previous slide" unless it has text or aria-label, aria-controls the Viewport; on the first page aria-disabled and still focusable | focus is not lost when the button stops working |
| Next | the same, named "Next slide", disabled on the last page | as Previous |
| AutoplayControl | a button named "Stop automatic slide show" while rotating and "Start automatic slide show" otherwise, with no aria-pressed | the name says what a press does; moving content can be stopped (WCAG 2.2.2) |
| TabList | role="tablist" named "Choose slide to display", one Tab stop, aria-orientation="vertical" when vertical | the pages are one control, crossed with the arrow keys |
| Tab | role="tab", aria-selected on the current page, aria-controls the page's slides, named "Slide 2" or "Slides 4 to 6" unless it has text | each tab says which slides it shows |
| Value | the text "2 of 5", not a live region | the position can be shown without being announced twice |
Focus order
- In document order. The examples follow the pattern's order: the
AutoplayControlfirst, thenPreviousandNext, then the current slide's content, then the tab of the current page. Nothing takes focus on mount. PreviousandNextkeep focus when they scroll, and when they are disabled at either end.- Slides out of view are skipped until they scroll in; with
offscreenSlides="interactive"every slide's content is reachable and the browser scrolls a focused one into view. - The
TabListis one Tab stop: the tab of the current page, or the tab that last had focus while focus stays in the list.