express-labs/pure-react-carousel
express-labs/pure-react-carousel has 1,698 GitHub stars and 162 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-08 02:27 UTC. Last push 2026-10-22.
A highly impartial suite of React components that can be assembled by the consumer to create a carousel with almost no limits on DOM structure or CSS styles. If you're tired of fighting some other developer's CSS and DOM structure, this carousel is for you.
Measured star growth
- in 24h
- 0
- in 7 days
- Collecting history
- in 15 days
- Collecting history
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalPure React Carousel
A highly impartial suite of React components that can be assembled by the consumer to create a responsive and aria compliant carousel with almost no limits on DOM structure or CSS styles.
See Live Examples | See Example Code | React Compatibility Chart
Motivation
My goal was to create a 100% ReactJS carousel that doesn't try to impose structure or styles that need to be defeated in order to match your site's design standards. Are you tired of fighting some other developer's CSS or DOM structure? If so, this carousel is for you.
Carousels: Love them or hate them. However, if you are a React developer, and you have to use a carousel, why not use one that was...
- Developed from the ground-up in React with modern functional components, hooks, and native React Context.
- Written in TypeScript with complete bundled type definitions and zero runtime dependencies.
- Is not a wrapper or port of some non-React carousel like Slick or Flickity.
- Fully supports touch and mouse drag events.
- Is ARIA compliant.
- Is responsive by default.
- Lets you assemble the carousel components in the DOM in any order you desire so long as they are all children of a single <CarouselProvider /> component.
- Lets you put any class names, properties, attributes, or styles on any of the components that you need.
- Supports ES Modules and CommonJS.
- Has 100% test coverage. Solid!
Table of contents
- 🛠 Tutorial
- Component Properties (props)
- Components
- TypeScript usage
- Advanced Concept: Accessing the "View" Components Directly
- Dev Workflow
- Contributors
🛠 Tutorial 🛠
Let's make a simple carousel with three slides, a next button, and a back button.
- Add the module to your project.
npm i -S pure-react-carousel
- Import only the required components into your project.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
- Using a bundler such as Webpack, Vite, or Rollup with a CSS loader? Import the stylesheet as well.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
- Put a
CarouselProvidercomponent in your code. This allows the other carousel components to communicate with each other via React Context. The only required properties aretotalSlides,naturalSlideWidth, andnaturalSlideHeight.naturalSlideWidthandnaturalSlideHeightare used to create an aspect ratio for each slide. Since the carousel is responsive by default, it will stretch to fill the width of its parent container.CarouselProvidermust also have children. We'll add the children in the next step.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
export default function MyCarousel() {
return (
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
/>
);
}
- Place the components in any order you wish as long as they are children of a single
CarouselProvidercomponent. Some components have ancestor/descendant relationships, but they don't have to be direct relatives. For example:Slidecomponents need to go inside of aSlider(orNativeSlider).Slidecomponents also require a sequentially numberedindexprop starting at zero. Chances are, a lot of the time, you're going to be populating the slides from data and looping through the data, so it is easy to pass the index in your loop.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
export default function MyCarousel() {
return (
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
>
<Slider>
<Slide index={0}>I am the first Slide.</Slide>
<Slide index={1}>I am the second Slide.</Slide>
<Slide index={2}>I am the third Slide.</Slide>
</Slider>
</CarouselProvider>
);
}
- Add some buttons so the user can navigate forward and backward. In order to use left/right arrow keyboard navigation, the
Slidercomponent needs to be focused. See the example in the Slider section.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
export default function MyCarousel() {
return (
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
>
<Slider>
<Slide index={0}>I am the first Slide.</Slide>
<Slide index={1}>I am the second Slide.</Slide>
<Slide index={2}>I am the third Slide.</Slide>
</Slider>
<ButtonBack>Back</ButtonBack>
<ButtonNext>Next</ButtonNext>
</CarouselProvider>
);
}
That's it. You have a super basic Carousel.
There are other components you can add, like NativeSlider (for native CSS overflow scrolling and CSS Scroll Snap), ButtonFirst, ButtonLast, ButtonPlay, Dot, DotGroup, Spinner, an Image component, and even an ImageWithZoom component that zooms on mouse hover or two-finger pinch/touch.
Obviously, you can customize the heck out of the layout. If you need to bury your Slider component in 18 parent divs, go for it. It will still do its job. Feel free to add the className property to any of the Components to further customize your carousel. Or, hook into the many BEM named default CSS class names built into the carousel components.
Some components have a ancestor / descendant relationship but they don't have to be direct descendants of the parent. For example, Slide needs to be a descendant of Slider, but you can put a bunch of div wrappers around slide if you need to. A good analogy are the html tags table and tr. The tr tag needs to be a descendant of table, but it doesn't have to be a direct descendant. You can have a tbody between them in the tree.
Component Properties (props)
className
You can attach your own className property to each and every component in this library and it will be appended to the list of classes. It's appended so that it has more specificity in a tie, allowing your CSS to more easily override any internal styles without resorting to using !important.
style
You can attach your own style property to components in this library; however, any required layout styles generated internally by a component take precedence where necessary for the carousel to function properly.
event props (onClick, onFocus, onBlur, etc)
You can supply your own event callbacks to any component. Your event callbacks are called after a component's internal event handling. Basically, your callback becomes a callback to our callback. Say that 10 times fast. :-)
ref
All public and unconnected *View components in this library support React.forwardRef and forward ref to their underlying root DOM element.
all other props
Any remaining props not consumed by the component are passed directly to the root element of a component unless otherwise noted in that component's documentation. This makes all the components in this library HIGHLY configurable. You can, for example, add your own event handlers, or change aria tags, etc.
Components
<CarouselProvider />
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the CarouselProvider needs to wrap other Carousel components and JSX. | |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| currentSlide | number | 0 | No | Sets the active <Slide /> index (0-indexed) on mount and dynamically updates the active slide whenever this prop changes. |
| disableAnimation | boolean | false | No | Disables the CSS transition animation when moving between slides and suppresses live touch/mouse drag translation of the slider tray while keeping swipe-to-advance navigation active. |
| disableKeyboard | boolean | false | No | When set to true, disables left/right arrow keyboard navigation on the <Slider />. |
| hasMasterSpinner | boolean | false | No | When true, a spinner will cover the <Slider /> component until all <Image /> and <ImageWithZoom /> components are done loading images. If there are no <Image /> or <ImageWithZoom /> components, the spinner will spin until this property is set to false. |
| interval | number | 5000 | No | Number of milliseconds to wait when the auto slideshow is active. |
| isPageScrollLocked | boolean | false | No | When true, locks scrolling on the nearest scrollable ancestor container (also managed automatically during vertical touch drags and pinch-zoom). |
| isPlaying | boolean | false | No | Setting this to true starts an auto slideshow. After interval milliseconds, the slider will move by step slides either forward or backward depending on the value of playDirection. |
| lockOnWindowScroll | boolean | false | No | When set to true, scrolling of the carousel slides is disabled while the browser window is scrolling. |
| naturalSlideHeight | number | Yes | The natural height of each <Slide /> component. | |
| naturalSlideWidth | number | Yes | The natural width of each <Slide /> component. | |
| orientation | ['horizontal'|'vertical'] | 'horizontal' | No | Possible values are 'horizontal' and 'vertical'. Lets you have a horizontal or vertical carousel. |
| pauseOnInteract | boolean | true | No | When set to true (default), user interactions (navigation buttons, dots, keyboard arrow keys, and dragging) pause the auto slideshow (isPlaying becomes false). When set to false, user interactions keep the auto slideshow playing and reset the interval timer. |
| playDirection | ['forward'|'backward'] | 'forward' | No | The direction for the auto slideshow. |
| snapAlign | ['start'|'center'|'end'|'none'] | 'start' | No | Default CSS scroll-snap-align value applied to each <Slide /> (can be overridden per <Slide />). Used with <NativeSlider />. |
| snapStop | ['normal'|'always'] | 'normal' | No | Default CSS scroll-snap-stop value applied to each <Slide /> (can be overridden per <Slide />). Used with <NativeSlider />. |
| snapType | ['mandatory'|'proximity'|'none'] | 'mandatory' | No | Default CSS scroll-snap-type strictness applied to <NativeSlider /> (can be overridden on <NativeSlider />). Use 'none' for free-momentum scrolling without snapping. |
| step | number | 1 | No | The number of slides to move when pressing the <ButtonBack /> and <ButtonNext /> buttons. |
| dragStep | number | 1 | No | The number of slides to move when performing a short touch or mouse drag. |
| tag | string | 'div' | No | The HTML element to use for the provider wrapper. |
| totalSlides | number | Yes | Always set this to match the total number of <Slide /> components in your carousel. | |
| touchEnabled | boolean | true | No | Set to true to enable touch drag events. |
| dragEnabled | boolean | true | No | Set to true to enable mouse dragging events. |
| visibleSlides | number | 1 | No | The number of slides to show at once. This number should be <= totalSlides. |
| infinite | boolean | false | No | Should the carousel continue (wrap around) or stop at the beginning or end of the slides. |
| isIntrinsicHeight | boolean | false | No | Disables the enforced height aspect ratio, and instead uses the intrinsic height of the slides. This option can only be active in 'horizontal' orientation; it will throw an error in 'vertical' orientation. |
The CarouselProvider component creates the following pseudo HTML by default:
<props.tag|div class="carousel [props.className]" ...props>
[props.children]
</props.tag|div>
More about naturalSlideWidth and naturalSlideHeight The carousel is responsive and by default will flex to the full width of the <Slider /> parent container. It's up to you to contain the carousel width via css. Each slide will be the same height to width ratio (intrinsic ratio). CarouselProvider needs to know the default size of each <Slide />. Note: you can make the carousel non-responsive by setting the width of <Slider /> to a fixed css unit, like pixels. There are many other ways to make the carousel non-responsive.
<Slider />
A Slider is a viewport that masks slides. The Slider component must wrap one or more Slide components.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the Slider needs to wrap other components and/or JSX. | |
| ariaLabel | string | 'slider' | No | Optional string that sets the aria-label attribute (omitted automatically when aria-labelledby is provided). |
| backwardKeyCode | number | 37 | No | Key code for moving backward via keyboard (defaults to 37, Left Arrow). |
| forwardKeyCode | number | 39 | No | Key code for moving forward via keyboard (defaults to 39, Right Arrow). |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| classNameAnimation | [string|null] | null | No | Optional className string. The slider uses the CSS transform property, applying translateX to move the slider tray horizontally and translateY to move the slider tray vertically. The animation is the result of applying a CSS transition effect. If you supply your own classNameAnimation class, the default transition is disabled and ONLY the transitions specified by your classNameAnimation class are applied. Learn more about CSS transitions. |
| classNameTray | [string|null] | null | No | Optional className string that is applied to the Slider's tray. The "tray" is the DOM element that contains the slides. The type of DOM element is specified by the trayTag property. |
| classNameTrayWrap | [string|null] | null | No | Optional className string that is applied to the div that surrounds the Slider's tray. |
| disableAnimation | boolean | false | No | Inherited from <CarouselProvider /> or overridden directly on <Slider /> to disable slide transition animations and suppress live touch/mouse drag translation of the slider tray while keeping swipe-to-advance navigation active. |
| disableKeyboard | boolean | false | No | Inherited from <CarouselProvider /> or overridden directly on <Slider /> to disable keyboard arrow navigation. |
| dragClickThreshold | number | 5 | No | Pointer movement threshold in pixels beyond which a mouse or touch drag gesture suppresses subsequent click events on child elements inside <Slide />. |
| moveThreshold | number | 0.1 | No | Threshold to control the drag distance that triggers a scroll to the next or previous slide (slideWidthOrHeight * moveThreshold = drag pixel distance required to scroll). |
| preventVerticalScrollOnTouch | boolean | true | No | When set to true and touch is enabled, prevents vertical screen scroll during horizontal touch drag gestures on the carousel. |
| horizontalPixelThreshold | number | 15 | No | The minimum number of pixels moved horizontally by touch on the carousel to block vertical page scroll when preventVerticalScrollOnTouch is true. |
| verticalPixelThreshold | number | 10 | No | The maximum number of pixels moved vertically by touch on the carousel within which vertical page scroll will be blocked when preventVerticalScrollOnTouch is true. |
| onMasterSpinner | [function|null] | null | No | Optional callback function that is called when the Master Spinner is visible. Requires that <CarouselProvider /> has hasMasterSpinner set to true. |
| role | string | 'listbox' | No | Optional string that sets the ARIA role attribute on the slider viewport element. |
| spinner | [function|null] | null | No | Optional render function to render your own custom spinner, e.g. () => <MyCustomSpinnerComponent />. If omitted, the default <Spinner /> is used. |
| style | object | {} | No | Optional CSS styles to add to the Slider root element. |
| trayProps | object | {} | No | Any props you want to attach to the slider tray element, with the exception of className, style, and ref. className is handled via classNameTray, and style/ref are managed internally. Event handlers such as onMouseDown, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, and onClickCapture are called after the Slider's internal event handlers. |
| trayTag | string | 'div' | No | The HTML tag to use for the tray (the element that holds all the slides and moves them back and forth). |
| trayWrapProps | object | {} | No | Additional HTML attributes or event handlers to attach to the tray wrapper div (excluding className, which is handled via classNameTrayWrap). |
The Slider component creates the following pseudo HTML by default.
<div class="carousel__slider [carousel__slider--vertical|carousel__slider--horizontal] [props.className]" aria-live="[off|polite]" aria-label="[props.ariaLabel]" role="[props.role]" style="[props.style]" ...props>
<div class="carousel__slider-tray-wrapper [carousel__slider-tray-wrap--vertical|carousel__slider-tray-wrap--horizontal] [props.classNameTrayWrap]" ...props.trayWrapProps>
<props.trayTag|div class="carousel__slider-tray [props.classNameAnimation] [carousel__slider-tray--vertical|carousel__slider-tray--horizontal] [props.classNameTray]" ...props.trayProps>
[props.children]
</props.trayTag|div>
<div class="carousel__master-spinner-container">
<div class="carousel__spinner" />
</div>
</div>
</div>
How to enable left/right arrow key navigation
While keyboard navigation to scroll between slides is supported by default, the Slider (or an element inside it) needs focus and a tabIndex in order to receive keyboard events. A simple solution looks like the following:
import React, { useEffect } from 'react';
import { Slider } from 'pure-react-carousel';
export function FocusableSlider() {
useEffect(() => {
const slider = document.getElementById('mySlider');
if (slider) {
slider.focus();
}
}, []);
return (
<Slider id="mySlider" tabIndex={0}>
{/* slides... */}
</Slider>
);
}
How to Change the Default Transition Animation
Custom Tray Slide Transition (
classNameAnimation):- Read the documentation for the
classNameAnimationproperty on theSlidercomponent and CSS transitions. - Create your own CSS class that defines a
transitionontransform(for example,transition: transform 650ms cubic-bezier(0.22, 1, 0.36, 1);). - Pass the CSS class to the
classNameAnimationprop of<Slider />to replace the built-in.sliderAnimationtransition.
- Read the documentation for the
Fade / Non-Sliding Transitions with Touch Swipe (
disableAnimation+classNameVisible/classNameHidden):- When using custom non-sliding transitions (such as cross-fade or opacity transitions), set
disableAnimation={true}on<CarouselProvider />(or<Slider />). disableAnimationsetstransition: noneon the slider tray and suppresses live finger/mousetranslateX/translateYdragging of the tray while keeping swipe-to-advance ontouchend/mouseupactive.- Pair this with
classNameVisibleandclassNameHiddenon<Slide />to animate opacity or scale when the active slide changes:
- When using custom non-sliding transitions (such as cross-fade or opacity transitions), set
import React from 'react';
import { CarouselProvider, Slider, Slide } from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
import styles from './FadeCarousel.module.css';
export function FadeCarousel() {
return (
<CarouselProvider
naturalSlideWidth={400}
naturalSlideHeight={500}
totalSlides={3}
visibleSlides={1}
disableAnimation
touchEnabled
>
<Slider>
<Slide index={0} classNameVisible={styles.slideVisible} classNameHidden={styles.slideHidden}>
Slide 1
</Slide>
<Slide index={1} classNameVisible={styles.slideVisible} classNameHidden={styles.slideHidden}>
Slide 2
</Slide>
<Slide index={2} classNameVisible={styles.slideVisible} classNameHidden={styles.slideHidden}>
Slide 3
</Slide>
</Slider>
</CarouselProvider>
);
}
<NativeSlider />
NativeSlider is an alternative slider viewport that uses native browser CSS overflow scrolling (overflow-x: auto / overflow-y: auto) and CSS Scroll Snap while working seamlessly with <CarouselProvider /> and the rest of the component suite (<Slide />, <ButtonBack />, <ButtonNext />, <ButtonFirst />, <ButtonLast />, <ButtonPlay />, <DotGroup />, <Dot />, <Spinner />, <Image />, and <ImageWithZoom />).
Active and visible slides are tracked without timers using IntersectionObserver alongside progressive enhancement for native scrollsnapchange and scrollend events. When snapType="none" is set on <CarouselProvider /> or <NativeSlider />, slides scroll with native momentum without snapping to slide boundaries.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, NativeSlider needs to wrap <Slide /> components and/or JSX. |
|
| ariaLabel | string | 'slider' | No | Optional string that sets the aria-label attribute (omitted automatically when aria-labelledby is provided). |
| backwardKeyCode | number | 37 | No | Key code for moving backward via keyboard (defaults to 37, Left Arrow). |
| forwardKeyCode | number | 39 | No | Key code for moving forward via keyboard (defaults to 39, Right Arrow). |
| className | [string|null] | null | No | Optional className string that will be appended to the component's root className string. |
| classNameTray | [string|null] | null | No | Optional className string applied to the inner tray element (trayTag). |
| classNameTrayWrap | [string|null] | null | No | Optional className string applied to the scrollable tray wrapper div. |
| disableAnimation | boolean | false | No | Inherited from <CarouselProvider /> or overridden directly on <NativeSlider />. When true, uses scroll-behavior: auto (behavior: 'instant') instead of smooth scrolling. |
| disableKeyboard | boolean | false | No | Inherited from <CarouselProvider /> or overridden directly on <NativeSlider /> to disable keyboard arrow navigation. |
| hideScrollbar | boolean | true | No | Hides the native browser scrollbar on the tray wrapper (scrollbar-width: none and ::-webkit-scrollbar { display: none }). Set to false to show the native scrollbar. |
| onMasterSpinner | [function|null] | null | No | Optional callback function called when the Master Spinner is visible (hasMasterSpinner={true}). |
| role | string | 'listbox' | No | Optional string that sets the ARIA role attribute on the slider viewport element. |
| snapType | ['mandatory'|'proximity'|'none'] | 'mandatory' | No | Inherited from <CarouselProvider /> or overridden directly on <NativeSlider />. Controls CSS scroll-snap-type strictness ('mandatory', 'proximity', or 'none' for free scrolling). |
| spinner | [function|null] | null | No | Optional render function to render your own custom spinner, e.g. () => <MyCustomSpinnerComponent />. If omitted, the default <Spinner /> is used. |
| style | object | {} | No | Optional CSS styles to add to the NativeSlider root element. |
| tabIndex | number | undefined | No | Optional tabIndex passed through to the root slider element. |
| trayProps | object | {} | No | Additional HTML attributes or event handlers to attach to the tray element (excluding className, style, and ref). |
| trayTag | string | 'div' | No | The HTML tag to use for the inner tray element that holds the slides. |
| trayWrapProps | object | {} | No | Additional HTML attributes or event handlers to attach to the scrollable tray wrapper div (excluding className and ref). |
The NativeSlider component creates the following pseudo HTML by default:
<div class="carousel__slider carousel__native-slider [carousel__slider--vertical|carousel__slider--horizontal] [carousel__native-slider--vertical|carousel__native-slider--horizontal] [props.className]" aria-live="[off|polite]" aria-label="[props.ariaLabel]" role="[props.role]" style="[props.style]" ...props>
<div class="carousel__slider-tray-wrapper carousel__native-slider-tray-wrapper [carousel__slider-tray-wrap--vertical|carousel__slider-tray-wrap--horizontal] [carousel__native-slider-tray-wrapper--snap-mandatory|carousel__native-slider-tray-wrapper--snap-proximity|carousel__native-slider-tray-wrapper--snap-none] [carousel__native-slider-tray-wrapper--hide-scrollbar] [carousel__native-slider-tray-wrapper--smooth-scroll|carousel__native-slider-tray-wrapper--instant-scroll] [props.classNameTrayWrap]" ...props.trayWrapProps>
<props.trayTag|div class="carousel__slider-tray carousel__native-slider-tray [carousel__slider-tray--vertical|carousel__slider-tray--horizontal] [props.classNameTray]" ...props.trayProps>
[props.children]
</props.trayTag|div>
<div class="carousel__master-spinner-container">
<div class="carousel__spinner" />
</div>
</div>
</div>
Example: Native Scroll Snap or Free-Momentum Scrolling
import React from 'react';
import {
CarouselProvider,
NativeSlider,
Slide,
ButtonBack,
ButtonNext,
DotGroup,
} from 'pure-react-carousel';
import 'pure-react-carousel/dist/react-carousel.es.css';
export function MyNativeCarousel() {
return (
<CarouselProvider
naturalSlideWidth={400}
naturalSlideHeight={300}
totalSlides={6}
visibleSlides={2}
snapType="proximity" // or "mandatory" (default) or "none" (free scroll without snapping)
snapAlign="start" // "start" (default) | "center" | "end" | "none"
snapStop="normal" // "normal" (default) | "always"
>
<NativeSlider>
<Slide index={0}>Slide 1</Slide>
<Slide index={1}>Slide 2</Slide>
<Slide index={2}>Slide 3</Slide>
<Slide index={3}>Slide 4</Slide>
<Slide index={4}>Slide 5</Slide>
<Slide index={5}>Slide 6</Slide>
</NativeSlider>
<ButtonBack>Back</ButtonBack>
<ButtonNext>Next</ButtonNext>
<DotGroup />
</CarouselProvider>
);
}
<Slide />
The Slide component is a container with an intrinsic ratio computed by the CarouselProvider naturalSlideWidth and naturalSlideHeight properties. By default, only one slide is visible in the Slider at a time. You can change this by altering the visibleSlides property of the CarouselProvider. Slide components also contain a div that acts as an aria compliant focus ring when the Slide receives focus either by using a keyboard tab, mouse click, or touch.
| property | type | default | required | purpose |
|---|---|---|---|---|
| ariaLabel | string | 'slide' | No | Optional string that sets the aria-label attribute (omitted automatically when aria-labelledby is provided). |
| children | [string|node|null] | null | No | Optional slide content. |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| classNameHidden | [string|null] | null | No | Optional className string that will be appended to the component's className string when the slide is not visible. |
| classNameVisible | [string|null] | null | No | Optional className string that will be appended to the component's className string when the slide is visible. |
| focusRing | boolean | true | No | Renders <div class="carousel__slide-focus-ring" /> inside the inner slide wrapper when the slide element itself is focused (ev.target === ev.currentTarget). Set to false to disable the inner focus ring element. |
| index | number | Yes | You must consecutively number the <Slide /> components in order starting with zero. | |
| inert | boolean | !isVisible |
No | Controls the HTML inert attribute on the slide element. Defaults to true when the slide is hidden (!isVisible) so off-screen interactive descendants are removed from the tab order and accessibility tree, or can be overridden explicitly. |
| innerClassName | [string|null] | null | No | Optional className string that will be appended to the inner slide wrapper element (carousel__inner-slide). |
| innerProps | object | {} | No | Additional HTML attributes or event handlers to attach to the inner slide wrapper element (innerTag), excluding className (handled via innerClassName). |
| innerTag | string | 'div' | No | The inner HTML element for each Slide. |
| isIntrinsicHeight | boolean | false | No | Inherited from <CarouselProvider /> or overridden on the Slide to use intrinsic height rather than aspect-ratio padding. |
| onBlur | [function|null] | null | No | Optional callback function that is called after the internal onBlur handler is called. Passed the React synthetic focus event. |
| onFocus | [function|null] | null | No | Optional callback function that is called after the internal onFocus handler is called. Passed the React synthetic focus event. |
| role | string | 'option' | No | Optional string that sets the ARIA role attribute on the slide element. Default aria-selected is only rendered when role is a selectable ARIA role ('option', 'tab', 'gridcell', 'row', 'treeitem') or when aria-selected is explicitly passed. |
| snapAlign | ['start'|'center'|'end'|'none'] | 'start' | No | Inherited from <CarouselProvider /> or overridden on the Slide to set CSS scroll-snap-align (carousel__slide--snap-align-*). |
| snapStop | ['normal'|'always'] | 'normal' | No | Inherited from <CarouselProvider /> or overridden on the Slide to set CSS scroll-snap-stop (carousel__slide--snap-stop-*). |
| style | object | {} | No | Optional CSS styles to merge onto the outer slide element. |
| tabIndex | number | undefined | No | Optional tabIndex passed through to the root slide element. |
| tag | string | 'div' | No | The root HTML element for each Slide. |
The Slide component creates the following pseudo HTML by default:
<props.tag|div class="carousel__slide [carousel__slide--focused] [props.classNameVisible|props.classNameHidden] [carousel__slide--visible|carousel__slide--hidden] [carousel__slide--snap-align-start|carousel__slide--snap-align-center|carousel__slide--snap-align-end|carousel__slide--snap-align-none] [carousel__slide--snap-stop-normal|carousel__slide--snap-stop-always] [props.className]" aria-selected="[isVisible]" aria-hidden="[!isVisible]" inert="[!isVisible]" aria-label="[props.ariaLabel]" role="[props.role]" onFocus="[props.onFocus]" onBlur="[props.onBlur]" style="[props.style]" ...props>
<props.innerTag|div class="carousel__inner-slide [props.innerClassName]" ...props.innerProps>
[props.children]
<!-- rendered when the slide element itself is focused and focusRing !== false -->
<div class="carousel__slide-focus-ring" />
</props.innerTag|div>
</props.tag|div>
<Dot />
A Dot component is an HTML button. Dots directly correlate to slides. Clicking on a dot causes its correlating slide to scroll into the left-most visible slot of the slider. The dots for currently visible slides are automatically disabled unless overridden via the disabled prop. When children has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="slide dot" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|null|node] | null | No | Children is a special React property. Basically, the Dot component wraps other components and/or JSX. |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | [boolean|null] | null | No | null means Dot will automatically determine if this button is disabled (disabled when the slide is currently visible). Setting this to true forces the button to be disabled. Setting this to false prevents the button from being disabled. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick handler is called. It is passed the React synthetic event. |
| selected | [boolean|null] | null | No | null means Dot will automatically determine if it is selected (when slide is within the currently visible range). Setting this to true or false overrides the selected state and carousel__dot--selected class. |
| slide | number | Yes | There must be a matching <Slide /> component with a matching index property. Example: <Dot slide={0} /> matches <Slide index={0} />. |
The Dot component creates the following pseudo HTML by default:
<button type="button" aria-label="slide dot" class="carousel__dot carousel__dot--[slide] [carousel__dot--selected] [props.className]" onClick="[props.onClick]" disabled="[props.disabled]" ...props>
[props.children]
</button>
<DotGroup />
A compound component that creates a group of <Dot /> buttons automatically for you.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node|null] | null | No | Any JSX wrapped by this component will appear AFTER the dots. |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| dotNumbers | boolean | false | No | Setting to true automatically adds text numbers to the dot buttons starting at 1. |
| dotProps | object | {} | No | Additional props spread onto every default <Dot /> rendered by <DotGroup />. |
| getDotProps | [function|null] | null | No | Optional callback (index, isSelected) => Partial<DotProps> returning per-dot props (such as aria-label, aria-current, or role) merged onto each default <Dot />. |
| disableActiveDots | boolean | true | No | When true (default), dots for currently active/selected slides are disabled. Setting to false makes all dots, including active dots, enabled. |
| showAsSelectedForCurrentSlideOnly | boolean | false | No | Setting to true shows only the currentSlide dot as selected, rather than all visibleSlides. |
| renderDots | [function|null] | null | No | Optional render function (props) => ReactNode that overrides the default dot rendering in <DotGroup />. |
The DotGroup component creates the following pseudo HTML by default:
<div class="carousel__dot-group [props.className]" ...props>
<!-- button repeats for each slide -->
<button type="button" aria-label="slide dot" class="carousel__dot carousel__dot--[slide] [carousel__dot--selected]" ...props.dotProps ...props.getDotProps(index, isSelected)>
<span class="carousel__dot-group-dot">[numbers or blank]</span>
</button>
[props.children]
</div>
<Image />
An image component that preloads images in memory, applies loading/success/error CSS classes, optionally renders custom loading or error fallbacks, and integrates with <CarouselProvider hasMasterSpinner />.
| property | type | default | required | purpose |
|---|---|---|---|---|
| alt | string | '' | No | Specifies alternate text for an image if the image cannot be displayed. On non-img tags (such as tag="div" with isBgImage), a non-empty alt sets role="img" and aria-label={alt} once loaded unless role, aria-label, or aria-labelledby is explicitly provided. |
| children | [string|node|null] | null | No | Any optional JSX wrapped by the Image component. |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| hasMasterSpinner | boolean | false | No | Automatically inherited from <CarouselProvider /> (or passed explicitly to <ImageView />). When true, the master spinner will cover the <Slider /> viewport until all <Image /> and <ImageWithZoom /> components have finished loading. |
| isBgImage | boolean | false | No | When set to true with a non-img tag (such as div), appends the carousel__image--with-background class and renders the image as a CSS background-image. Note: do not use isBgImage with tag="img". |
| onError | [function|null] | null | No | Callback function called if the image defined by src fails to load. Fired after the Image component's internal error handler. |
| onLoad | [function|null] | null | No | Callback function called if the image defined by src loads successfully. Fired after the Image component's internal load handler. |
| renderError | [function|null] | null | No | When defined, if an image fails to load, this function is called and its returned JSX is rendered instead of children. |
| renderLoading | [function|null] | null | No | When defined, this function is called while the image is loading and its returned JSX is rendered instead of children. |
| src | string | Yes | URL of the image. | |
| style | [object|null] | null | No | Optional CSS inline styles applied to the element. |
| tag | string | 'img' | No | The element that will receive the image once loaded. While loading or in an error state, a div is rendered if tag is 'img'. Any tag besides 'img' (e.g. 'div') renders the loaded image via CSS backgroundImage and backgroundSize: 'cover'. |
The Image component creates the following pseudo HTML by default:
<!-- While loading (or error): -->
<div class="carousel__image [carousel__image--with-background] carousel__image--loading|carousel__image--error [props.className]" ...props>
[props.renderLoading() | props.renderError() | props.children]
</div>
<!-- When loaded with tag="img": -->
<img class="carousel__image carousel__image--success [props.className]" src="[props.src]" alt="[props.alt]" style="[props.style]" ...props />
<!-- When loaded with tag="div" and isBgImage: -->
<div class="carousel__image carousel__image--with-background carousel__image--success [props.className]" role="[img when props.alt is non-empty]" aria-label="[props.alt when non-empty]" style="background-image: url('[props.src]'); background-size: cover; [props.style]" ...props>
[props.children]
</div>
<ImageWithZoom />
A compound component that wraps <Image /> to provide hover zoom on desktop (mouse events), keyboard zoom and panning when focused via overlayProps, and two-finger pinch-to-zoom on touch devices.
| property | type | default | required | purpose |
|---|---|---|---|---|
| alt | string | undefined | No | Alternate text passed to the underlying display <Image />. |
| bgImageProps | object | {} | No | Additional props spread onto the underlying display <Image /> component. |
| bgImageTag | string | 'div' | No | The HTML tag used for the underlying display <Image /> (e.g. 'div' or 'img'). |
| className | [string|null] | null | No | Optional className string appended to the outer container element. |
| imageClassName | [string|null] | null | No | Optional className string appended to the base <Image /> (carousel__zoom-image). |
| overlayClassName | [string|null] | null | No | Optional className string appended to the zoom overlay <Image /> (carousel__zoom-image-overlay). |
| overlayProps | object | {} | No | Additional HTML attributes or event handlers spread onto the zoom overlay element (supports keyboard zoom/pan via Enter/Space/+/= to toggle zoom, arrow keys to pan by 10%, and Escape/- to exit zoom). |
| isPinchZoomEnabled | boolean | true | No | Enables two-finger pinch-to-zoom on touch-capable devices. |
| onError | [function|null] | null | No | Optional callback function called if the image fails to load. |
| onLoad | [function|null] | null | No | Optional callback function called when the image finishes loading. |
| spinner | [function|null] | null | No | Optional render function () => ReactNode to render a custom loading spinner while the image loads. Defaults to <Spinner />. |
| src | string | Yes | URL of the standard resolution image. | |
| srcZoomed | [string|null] | null | No | Optional URL of a higher-resolution image to use for the zoom overlay. If omitted, src is used. |
| tag | string | 'div' | No | The HTML tag to use for the outer wrapper element. |
The ImageWithZoom component creates the following pseudo HTML by default:
<props.tag|div class="[props.className]" ...props>
<props.bgImageTag|div class="carousel__image carousel__zoom-image [props.imageClassName]" ...props.bgImageProps />
<div class="carousel__image carousel__image--with-background carousel__zoom-image-overlay [carousel__zoom-image-overlay--hovering] [carousel__zoom-image-overlay--zooming] [props.overlayClassName]" ...props.overlayProps />
<!-- rendered while image is loading -->
<div class="carousel__image-loading-spinner-container">
<div class="carousel__spinner" />
</div>
</props.tag|div>
<Spinner />
The default loading spinner component used by <Slider /> and <ImageWithZoom />. Can also be imported and used standalone.
| property | type | default | required | purpose |
|---|---|---|---|---|
| className | [string|null] | null | No | Optional className string appended to the spinner element. |
The Spinner component creates the following pseudo HTML by default:
<div class="carousel__spinner [props.className]" ...props />
<ButtonBack />
A button for moving the slider backwards. Backwards on a horizontal carousel means "move to the left". Backwards on a vertical carousel means "move to the top". The slider will traverse an amount of slides determined by the step property of CarouselProvider. When children has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="previous" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the ButtonBack component needs to wrap other components and/or JSX | |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | [boolean|null] | null | No | Null means ButtonBack will automatically determine if this button is disabled. Setting this to true will force the button to be disabled. Setting this to false will prevent the button from ever being disabled. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick function is called. It is passed the React synthetic event |
The ButtonBack component creates the following pseudo HTML by default:
<button type="button" aria-label="previous" class="carousel__back-button [props.className]" onClick="[props.onClick]" disabled="[props.disabled]" ...props>
[props.children]
</button>
<ButtonNext />
A button for moving the slider forwards. Forwards on a horizontal carousel means "move to the right". Backwards on a vertical carousel means "move to the bottom". The slider will traverse an amount of slides determined by the step property of CarouselProvider. When children has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="next" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the ButtonNext component needs to wrap other components and/or JSX | |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | [boolean|null] | null | No | Null means ButtonNext will automatically determine if this button is disabled. Setting this to true will force the button to be disabled. Setting this to false will prevent the button from ever being disabled. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick function is called. It is passed the React synthetic event |
The ButtonNext component creates the following pseudo HTML by default:
<button type="button" aria-label="next" class="carousel__next-button [props.className]" onClick="[props.onClick]" disabled="[props.disabled]" ...props>
[props.children]
</button>
<ButtonFirst />
Moves the slider to the beginning of the slides. When children has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="first" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the ButtonFirst component needs to wrap other components and/or JSX | |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | [boolean|null] | null | No | Null means ButtonFirst will automatically determine if this button is disabled. Setting this to true will force the button to be disabled. Setting this to false will prevent the button from ever being disabled. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick function is called. It is passed the React synthetic event |
The ButtonFirst component creates the following pseudo HTML by default:
<button type="button" aria-label="first" class="carousel__first-button [props.className]" onClick="[props.onClick]" disabled="[props.disabled]" ...props>
[props.children]
</button>
<ButtonLast />
Moves the slider to the end of the slides (totalSlides - visibleSlides). When children has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="last" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | Yes | Children is a special React property. Basically, the ButtonLast component needs to wrap other components and/or JSX | |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | [boolean|null] | null | No | Null means ButtonLast will automatically determine if this button is disabled. Setting this to true will force the button to be disabled. Setting this to false will prevent the button from ever being disabled. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick function is called. It is passed the React synthetic event |
The ButtonLast component creates the following pseudo HTML by default:
<button type="button" aria-label="last" class="carousel__last-button [props.className]" onClick="[props.onClick]" disabled="[props.disabled]" ...props>
[props.children]
</button>
<ButtonPlay />
Pressing this button causes the slides to automatically advance by CarouselProvider's step property after an interval determined by CarouselProvider's interval property. When the active content (childrenPlaying / childrenPaused and children) has no accessible text content and neither aria-label nor aria-labelledby is provided, aria-label="play" is applied by default.
| property | type | default | required | purpose |
|---|---|---|---|---|
| children | [string|node] | null | No | Children is a special React property. Content wrapped by ButtonPlay will appear AFTER the content of childrenPaused and childrenPlaying |
| childrenPaused | [string|node] | null | No | Content to display when the slide show is paused. |
| childrenPlaying | [string|node] | null | No | Content to display when the slide show is playing. |
| className | [string|null] | null | No | Optional className string that will be appended to the component's className string. |
| disabled | boolean | false | No | Optional boolean passed to the underlying <button> element to disable the play/pause button. |
| onClick | [function|null] | null | No | Optional callback function that is called after the internal onClick function is called. It is passed the React synthetic event |
The ButtonPlay component creates the following pseudo HTML by default:
<button type="button" aria-label="play" class="carousel__play-button [props.className]" onClick="[props.onClick]" ...props>
[props.childrenPlaying | props.childrenPaused]
[props.children]
</button>
Hooks and useContext
CarouselProvider uses React's native Context API (CarouselContext) and automatically triggers re-renders on consumers whenever the carousel store state updates.
You can consume CarouselContext directly with useContext(CarouselContext) in any functional component rendered inside <CarouselProvider>:
import React, { useContext } from 'react';
import { CarouselContext } from 'pure-react-carousel';
export function MyComponentUsingContext() {
const carouselContext = useContext(CarouselContext);
const { currentSlide, totalSlides } = carouselContext.state;
return (
<div>
<p>Slide {currentSlide + 1} of {totalSlides}</p>
<button
type="button"
onClick={() => carouselContext.setStoreState({ currentSlide: 0 })}
>
Back to First Slide
</button>
</div>
);
}
For backwards compatibility (or if you need an imperative callback when state changes), carouselContext.subscribe(callback) and carouselContext.unsubscribe(callback) are also supported:
import React, { useContext, useEffect, useState } from 'react';
import { CarouselContext } from 'pure-react-carousel';
export function MySubscribedComponent() {
const carouselContext = useContext(CarouselContext);
const [currentSlide, setCurrentSlide] = useState(carouselContext.state.currentSlide);
useEffect(() => {
function onChange() {
setCurrentSlide(carouselContext.state.currentSlide);
}
carouselContext.subscribe(onChange);
return () => carouselContext.unsubscribe(onChange);
}, [carouselContext]);
return `The current slide is: ${currentSlide}`;
}
WithStore() Higher Order Component
NOTE: ADVANCED USE ONLY. (For new functional components, prefer useContext(CarouselContext) above.)
Use this HOC to pass CarouselProvider state properties as props to a component (either a functional component or a class component). Your custom component must be a descendant of <CarouselProvider>. It doesn't have to be a direct descendant; it just needs to be rendered somewhere inside <CarouselProvider>:
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
>
<YourComponentHere />
</CarouselProvider>
WithStore accepts two arguments:
WithStore(Component, [mapStateToProps])
- The first argument is the component to wrap (e.g.,
YourComponentHere) and is required. Both functional and class components are supported. - The second argument is an optional
mapStateToPropsfunction(state) => ({ ... })that maps the currentCarouselProviderstate to props passed to your component.
Here is an example showing the properties available on CarouselProvider state:
import React from 'react';
import { WithStore } from 'pure-react-carousel';
function YourComponentHere(props) {
return <div>Current slide: {props.currentSlide}</div>;
}
export default WithStore(YourComponentHere, (state) => ({
// State properties are deeply frozen (read-only).
// Pick only the properties your component needs:
currentSlide: state.currentSlide,
disableAnimation: state.disableAnimation,
disableKeyboard: state.disableKeyboard,
hasMasterSpinner: state.hasMasterSpinner,
imageErrorCount: state.imageErrorCount,
imageSuccessCount: state.imageSuccessCount,
infinite: state.infinite,
interval: state.interval,
isIntrinsicHeight: state.isIntrinsicHeight,
isPageScrollLocked: state.isPageScrollLocked,
isPlaying: state.isPlaying,
lockOnWindowScroll: state.lockOnWindowScroll,
masterSpinnerFinished: state.masterSpinnerFinished,
masterSpinnerThreshold: state.masterSpinnerThreshold,
naturalSlideHeight: state.naturalSlideHeight,
naturalSlideWidth: state.naturalSlideWidth,
orientation: state.orientation,
pauseOnInteract: state.pauseOnInteract,
playDirection: state.playDirection,
slideSize: state.slideSize,
slideTraySize: state.slideTraySize,
snapAlign: state.snapAlign,
snapStop: state.snapStop,
snapType: state.snapType,
step: state.step,
dragStep: state.dragStep,
totalSlides: state.totalSlides,
touchEnabled: state.touchEnabled,
dragEnabled: state.dragEnabled,
visibleSlides: state.visibleSlides,
}));
Any component wrapped with WithStore also receives a prop called carouselStore, which exposes getStoreState and setStoreState to read or update the CarouselProvider's state.
setStoreState: 🦄 🌈 🎂 Use this to update any of the state properties listed above. For example, to skip to slide 2:
import React from 'react';
import { WithStore } from 'pure-react-carousel';
function YourComponentHere({ carouselStore }) {
const handleClick = () => {
carouselStore.setStoreState({ currentSlide: 2 });
};
return (
<button type="button" onClick={handleClick}>
Go to slide 3
</button>
);
}
export default WithStore(YourComponentHere);
Fun fact: you can add any arbitrary values that you want to the CarouselProvider's state. So, if you have several custom components that need to share data, have at it.
masterSpinnerError: 💀 DON'T USE THIS (internal use only).
masterSpinnerSuccess: ⚠️ DON'T USE THIS (internal use only).
subscribeMasterSpinner: 💩 DON'T USE THIS (internal use only).
unsubscribeMasterSpinner: 🔥 DON'T USE THIS (internal use only).
unsubscribeAllMasterSpinner: Don't call this manually unless you have some sort of super-customized carousel. This is called internally once all <Image hasMasterSpinner /> and all <ImageWithZoom hasMasterSpinner /> components are finished loading their images. Calling this directly will force a "success" state and the master spinner (the spinner that covers the entire carousel while loading) will turn off.
TypeScript usage
Pure React Carousel is written in TypeScript and ships with complete type definitions in dist/types/index.d.ts. Simply import components, hooks, or types directly from 'pure-react-carousel'.
Hooks and useContext
CarouselContext is typed as React.Context<CarouselStoreInterface>, giving full type inference for state (CarouselState) and setStoreState:
import React, { useContext } from 'react';
import { CarouselContext } from 'pure-react-carousel';
export function SlideCounter() {
const carouselStore = useContext(CarouselContext);
return (
<button
type="button"
onClick={() => carouselStore.setStoreState({ currentSlide: 0 })}
>
Slide {carouselStore.state.currentSlide + 1} of {carouselStore.state.totalSlides}
</button>
);
}
WithStore() Higher Order Component
Both functional components and class components (React.ComponentType) are supported by WithStore. The state and injected store interfaces are exported from 'pure-react-carousel':
export interface CarouselState {
readonly currentSlide: number;
readonly disableAnimation: boolean;
readonly disableKeyboard: boolean;
readonly hasMasterSpinner: boolean;
readonly imageErrorCount: number;
readonly imageSuccessCount: number;
readonly infinite: boolean;
readonly interval?: number;
readonly isIntrinsicHeight: boolean;
readonly isPageScrollLocked?: boolean;
readonly isPlaying: boolean;
readonly lockOnWindowScroll: boolean;
readonly masterSpinnerFinished: boolean;
readonly masterSpinnerThreshold?: number;
readonly MasterSpinnerThreshold?: number;
readonly naturalSlideHeight: number;
readonly naturalSlideWidth: number;
readonly orientation: 'horizontal' | 'vertical';
readonly pauseOnInteract?: boolean;
readonly playDirection?: 'forward' | 'backward';
readonly privateUnDisableAnimation?: boolean;
readonly slideSize: number;
readonly slideTraySize: number;
readonly snapAlign?: 'start' | 'center' | 'end' | 'none';
readonly snapStop?: 'normal' | 'always';
readonly snapType?: 'mandatory' | 'proximity' | 'none';
readonly step: number;
readonly dragStep: number;
readonly totalSlides: number;
readonly touchEnabled: boolean;
readonly dragEnabled: boolean;
readonly visibleSlides: number;
}
export interface CarouselInjectedProps {
readonly carouselStore: Pick<
CarouselStoreInterface,
| 'getStoreState'
| 'masterSpinnerError'
| 'masterSpinnerSuccess'
| 'setStoreState'
| 'subscribeMasterSpinner'
| 'unsubscribeAllMasterSpinner'
| 'unsubscribeMasterSpinner'
>;
}
Examples:
- With
mapStateToPropsand custom props:
import React from 'react';
import {
CarouselInjectedProps,
CarouselProvider,
WithStore,
} from 'pure-react-carousel';
interface CustomProps {
readonly name: string;
}
interface MappedStateProps {
readonly currentSlide: number;
readonly disableAnimation: boolean;
}
function InjectedComponent({
name,
currentSlide,
disableAnimation,
carouselStore,
}: CustomProps & MappedStateProps & CarouselInjectedProps) {
return (
<div>
<span>{name}: Slide {currentSlide} (animation disabled: {String(disableAnimation)})</span>
<button
type="button"
onClick={() => carouselStore.setStoreState({ currentSlide: 0 })}
>
Reset
</button>
</div>
);
}
const DecoratedComponent = WithStore<CustomProps, MappedStateProps>(
InjectedComponent,
(state) => ({
currentSlide: state.currentSlide,
disableAnimation: state.disableAnimation,
}),
);
// Usage inside <CarouselProvider>:
// <DecoratedComponent name="MyCarousel" />
- Without
mapStateToProps:
import React from 'react';
import { CarouselInjectedProps, WithStore } from 'pure-react-carousel';
interface CustomProps extends CarouselInjectedProps {
readonly name: string;
}
function InjectedComponent({ name, carouselStore }: CustomProps) {
return (
<button
type="button"
onClick={() => carouselStore.setStoreState({ currentSlide: 0 })}
>
{name}
</button>
);
}
const DecoratedComponent = WithStore(InjectedComponent);
Advanced Concept: Accessing the "View" Components Directly
All the components in Pure React Carousel were designed using the container (model) -> presentation (view) pattern.
The index.ts file for each component connects to CarouselContext via the WithStore HOC, selects the required state properties from the carousel store, and passes them to the presentation component as props.
For example, look at the structure of the Slide component folder:
Slide/
├─ index.ts (container / store connector)
├─ Slide.tsx (view)
The index.ts file uses the WithStore HOC to provide Slide.tsx (the view) with all the data it needs from the carousel state—such as currentSlide, naturalSlideWidth, naturalSlideHeight, slideSize, totalSlides, visibleSlides, etc.
If you want direct access to the unconnected view component (for example, in unit tests or custom state wrappers), you can import it directly by adding View to the end of the component name. This works for all components except CarouselProvider:
import { SlideView } from 'pure-react-carousel';
Dev Workflow
Prerequisites
- Node.js: v24.18.0 (
>=24.0.0), pinned in.nvmrc,.node-version, andpackage.json. - Vite Plus (
vp) (optional global CLI): This repository uses Vite Plus (vite-plus) for its dev server, bundler, test runner, linter, formatter, and git hooks. All workflows run out of the box via standardnpm run <script>commands using the localvite-plusdependency, or you can optionally install the globalvpCLI (npm install -g vite-plus) to runvpcommands directly.
Contributor Installation & Setup
# 1. Clone the repository
git clone https://github.com/express-labs/pure-react-carousel.git
cd pure-react-carousel
# 2. Activate the pinned Node.js version (v24.18.0)
nvm use # or: vp env use
# 3. Install dependencies (also configures pre-commit hooks via `vp config`)
npm install # or `npm ci` for a clean lockfile install
# 4. Start the local interactive demo app
npm start
Project Structure
src/— Published library source code, component SCSS modules, and co-located unit/SSR tests (__tests__/).dev/— Interactive local development and GitHub Pages demo SPA (dev/index.html,dev/app.tsx,dev/compatibility.tsx, anddev/App/). Excluded from the published npm package.typings/— Compile-time TypeScript consumer contract tests (typings/index.test-d.tsx).vite.config.ts— Unified Vite Plus configuration for the dev server (vp dev), demo build (vp build), library packaging (vp pack), Vitest (vp test), Oxlint (vp lint), Oxfmt (vp fmt), and pre-commit hooks (staged).
Development Commands
npm start(vp dev) — Starts the local development server for the interactive demo app (dev/).npm test(vp test run --coverage) — Runs unit, integration, and SSR tests using Vitest and React Testing Library (RTL) with V8 coverage reporting.npm run test:watch(vp test) — Runs Vitest in interactive watch mode.npm run typecheck— Generates TypeScript declarations (dist/types/index.d.ts) and type-checks the library, demo app, and consumer typing contract (tsconfig.json&tsconfig.consumer.json).npm run lint/npm run lint:fix(vp lint) — Runs linting acrosssrc,dev,typings, andvite.config.tsusing Oxlint.npm run format/npm run format:check(vp fmt) — Formats or checks formatting acrosssrc,dev,typings, andvite.config.tsusing Oxfmt.npm run build(vp pack && npm run build:types) — Bundles the ES module (dist/index.es.js), CommonJS (dist/index.cjs.js), CSS (dist/react-carousel.es.css,dist/react-carousel.cjs.css), and TypeScript declaration (dist/types/index.d.ts) artifacts intodist/using Vite Plus (vp pack) and TypeScript (tsc).npm run build:demo(vp build) — Builds the static demo SPA (dev/) intodist-demo/for GitHub Pages deployment.npm run test:compat— Runs the automated multi-version React peer-dependency compatibility matrix across React 16.8, 17, 18, and 19.npm run prepublishOnly— Runs all verification gates in sequence (typecheck,lint,test,build).
Contributors
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Explore related projects
freeCodeCamp/freeCodeCamp
freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.
458,738 starsdonnemartin/system-design-primer
Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.
377,301 starsreact/react
The library for web and native user interfaces.
252,400 starsvuejs/vue
This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
212,630 stars