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**](https://express-labs.github.io/pure-react-carousel/) | [See Example Code](dev/App/examples) | [React Compatibility Chart](https://express-labs.github.io/pure-react-carousel/compatibility.html)

[![Build Status](https://github.com/express-labs/pure-react-carousel/actions/workflows/publish.yml/badge.svg)](https://github.com/express-labs/pure-react-carousel/actions/workflows/publish.yml) [![Known Vulnerabilities](https://snyk.io/test/github/express-labs/pure-react-carousel/badge.svg)](https://snyk.io/test/github/express-labs/pure-react-carousel) [![version](https://img.shields.io/npm/v/pure-react-carousel.svg?style=flat-square)](https://www.npmjs.com/package/pure-react-carousel) [![downloads](https://img.shields.io/npm/dm/pure-react-carousel.svg?style=flat-square)](http://www.npmtrends.com/pure-react-carousel) [![MIT License](https://img.shields.io/npm/l/pure-react-carousel.svg?style=flat-square)](https://github.com/express-labs/pure-react-carousel/blob/master/LICENSE) [![All Contributors](https://img.shields.io/badge/all_contributors-29-orange.svg?style=flat-square)](#contributors) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Code of Conduct](https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square)](https://github.com/express-labs/pure-react-carousel/blob/master/CODE_OF_CONDUCT.md) ## 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](#-tutorial-) * [Component Properties (props)](#component-properties-props) * [Components](#components) * [<CarouselProvider />](#carouselprovider-) * [<Slider />](#slider-) * [<NativeSlider />](#nativeslider-) * [<Slide />](#slide-) * [<Dot />](#dot-) * [<DotGroup />](#dotgroup-) * [<Image />](#image-) * [<ImageWithZoom />](#imagewithzoom-) * [<Spinner />](#spinner-) * [<ButtonBack />](#buttonback-) * [<ButtonNext />](#buttonnext-) * [<ButtonFirst />](#buttonfirst-) * [<ButtonLast />](#buttonlast-) * [<ButtonPlay />](#buttonplay-) * [Hooks and `useContext`](#hooks-and-usecontext) * [WithStore() Higher Order Component](#withstore-higher-order-component) * [TypeScript usage](#typescript-usage) * [Hooks and `useContext`](#hooks-and-usecontext-1) * [WithStore() Higher Order Component](#withstore-higher-order-component-1) * [Examples](#examples) * [Advanced Concept: Accessing the "View" Components Directly](#advanced-concept-accessing-the-view-components-directly) * [Dev Workflow](#dev-workflow) * [Contributors](#contributors) ## πŸ›  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` 2. Import only the required components into your project. ```jsx import React from 'react'; import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel'; ``` 3. Using a bundler such as Webpack, Vite, or Rollup with a CSS loader? Import the stylesheet as well. ```jsx import React from 'react'; import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel'; import 'pure-react-carousel/dist/react-carousel.es.css'; ``` 4. 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. ```jsx 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 ( ); } ``` 5. 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. ```jsx 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 ( I am the first Slide. I am the second Slide. I am the third Slide. ); } ``` 6. 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](#slider-). ```jsx 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 ( I am the first Slide. I am the second Slide. I am the third Slide. Back Next ); } ``` 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 `` (can be overridden per ``). Used with ``. | | snapStop | ['normal'|'always'] | 'normal' | No | Default CSS `scroll-snap-stop` value applied to each `` (can be overridden per ``). Used with ``. | | snapType | ['mandatory'|'proximity'|'none'] | 'mandatory' | No | Default CSS `scroll-snap-type` strictness applied to `` (can be overridden on ``). 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: ```HTML [props.children] ``` **__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](https://alistapart.github.io/code-samples/creating-intrinsic-ratios-for-video/example2.html)). 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](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_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 `` or overridden directly on `` 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 `` or overridden directly on `` 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 ``. | | 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. `() => `. If omitted, the default `` 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. ```HTML