Skip to content
Projects

express-labs/pure-react-carousel

View on GitHub

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.

How these counts are measured

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

Original

Pure React Carousel

Created By
Express Logo

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


Build Status Known Vulnerabilities version downloads MIT License

All Contributors PRs Welcome Code of Conduct

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 🛠

Let's make a simple carousel with three slides, a next button, and a back button.

  1. Add the module to your project.

npm i -S pure-react-carousel

  1. Import only the required components into your project.
import React from 'react';
import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel';
  1. 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';
  1. Put a CarouselProvider component in your code. This allows the other carousel components to communicate with each other via React Context. The only required properties are totalSlides, naturalSlideWidth, and naturalSlideHeight. naturalSlideWidth and naturalSlideHeight are 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. CarouselProvider must 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}
    />
  );
}
  1. Place the components in any order you wish as long as they are children of a single CarouselProvider component. Some components have ancestor/descendant relationships, but they don't have to be direct relatives. For example: Slide components need to go inside of a Slider (or NativeSlider). Slide components also require a sequentially numbered index prop 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>
  );
}
  1. Add some buttons so the user can navigate forward and backward. In order to use left/right arrow keyboard navigation, the Slider component 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

  1. Custom Tray Slide Transition (classNameAnimation):

    • Read the documentation for the classNameAnimation property on the Slider component and CSS transitions.
    • Create your own CSS class that defines a transition on transform (for example, transition: transform 650ms cubic-bezier(0.22, 1, 0.36, 1);).
    • Pass the CSS class to the classNameAnimation prop of <Slider /> to replace the built-in .sliderAnimation transition.
  2. 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 />).
    • disableAnimation sets transition: none on the slider tray and suppresses live finger/mouse translateX/translateY dragging of the tray while keeping swipe-to-advance on touchend / mouseup active.
    • Pair this with classNameVisible and classNameHidden on <Slide /> to animate opacity or scale when the active slide changes:
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 mapStateToProps function (state) => ({ ... }) that maps the current CarouselProvider state 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 mapStateToProps and 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, and package.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 standard npm run <script> commands using the local vite-plus dependency, or you can optionally install the global vp CLI (npm install -g vite-plus) to run vp commands 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, and dev/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 across src, dev, typings, and vite.config.ts using Oxlint.

  • npm run format / npm run format:check (vp fmt) — Formats or checks formatting across src, dev, typings, and vite.config.ts using 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 into dist/ using Vite Plus (vp pack) and TypeScript (tsc).

  • npm run build:demo (vp build) — Builds the static demo SPA (dev/) into dist-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):


Matthew Toledo

💻 🎨 👀 🤔 📆

Timothy Steele

💻 🎨 👀 🤔 📆

Jedrzej Lewandowski

💻

Justin Little

💻

Christoffer Palm

💻

Dylan Pyle

💻

Martin Schwier

💻

Arnaud Ambroselli

💻

Chung

💻

Federico Dionisi

💻

Jakub Jastrzębski

💻

Jean-Paul Delimat

💻

Martin

💻

Per Enström

💻

Serafino Messina

💻

Seth Killian

💻

Tim Chen

💻

Zoey Zou

💻

djshapiro

💻

Brandon Carroll

💻 🚧

Justin Conner

💻

Stéphane Klein

📖

Kevyn Bruyere

💻

Andy Schuler

💻

Mario Hoyos

💻

Nick Randall

👀

Carson Full

💻

Kristaps

💻

Pavel

💻

Sean LeBlanc

💻

Sebastian Martha

💻

Hannes Güdelhöfer

💻

Zach Levine

💻

Alec WM

💻

zhongdeliu

💻

Niko Savas

💻

Bret

💻

Brian B. Canin

💬

This project follows the all-contributors specification. Contributions of any kind welcome!

Explore related projects