DOM-first, headless carousel for React, utilizing native browser scrolling & CSS scroll snap points
npm install react-snap-carousel



DOM-first, headless carousel for React.
React Snap Carousel leaves the DOM in charge of scrolling and simply computes derived state from the layout, allowing you to progressively enhance a scroll element with responsive carousel controls.
š§ Utilizes native browser scrolling & CSS scroll snap points for best performance and UX
š Computes responsive page state from DOM layout & scroll position
š² Dynamic page-based CSS snap point rendering
š Headless design, giving you full control over UI using React Hooks API
šļø Written in TypeScript
šŖ¶ Lightweight (~1kB) + zero dependencies
```
npm install react-snap-carousel
š„StoryBook Examplesš„
React Snap Carousel doesn't expose a ready-made component and instead offers a single export useSnapCarousel which provides the state & functions necessary to build your own carousel component.
The following code snippet is a good starting point.
> Inline styles are used for simplicity. You can use whichever CSS framework you prefer.
> You can see it in action on CodeSandbox.
`tsx
// Carousel.tsx
import React, { CSSProperties } from 'react';
import { useSnapCarousel } from 'react-snap-carousel';
const styles = {
root: {},
scroll: {
position: 'relative',
display: 'flex',
overflow: 'auto',
scrollSnapType: 'x mandatory'
},
item: {
width: '250px',
height: '250px',
flexShrink: 0
},
itemSnapPoint: {
scrollSnapAlign: 'start'
},
controls: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
nextPrevButton: {},
nextPrevButtonDisabled: { opacity: 0.3 },
pagination: {
display: 'flex'
},
paginationButton: {
margin: '10px'
},
paginationButtonActive: { opacity: 0.3 },
pageIndicator: {
display: 'flex',
justifyContent: 'center'
}
} satisfies Record
interface CarouselProps
readonly items: T[];
readonly renderItem: (
props: CarouselRenderItemProps
) => React.ReactElement
}
interface CarouselRenderItemProps
readonly item: T;
readonly isSnapPoint: boolean;
}
export const Carousel =
items,
renderItem
}: CarouselProps
const {
scrollRef,
pages,
activePageIndex,
hasPrevPage,
hasNextPage,
prev,
next,
goTo,
snapPointIndexes
} = useSnapCarousel();
return (
{items.map((item, i) =>
renderItem({
item,
isSnapPoint: snapPointIndexes.has(i)
})
)}
style={{
...styles.nextPrevButton,
...(!hasPrevPage ? styles.nextPrevButtonDisabled : {})
}}
onClick={() => prev()}
disabled={!hasPrevPage}
>
Prev
{pages.map((_, i) => (
key={i}
style={{
...styles.paginationButton,
...(activePageIndex === i ? styles.paginationButtonActive : {})
}}
onClick={() => goTo(i)}
>
{i + 1}
))}
style={{
...styles.nextPrevButton,
...(!hasNextPage ? styles.nextPrevButtonDisabled : {})
}}
onClick={() => next()}
disabled={!hasNextPage}
>
Next
{activePageIndex + 1} / {pages.length}
);
};
interface CarouselItemProps {
readonly isSnapPoint: boolean;
readonly children?: React.ReactNode;
}
export const CarouselItem = ({ isSnapPoint, children }: CarouselItemProps) => (
`tsx
// App.tsx
import { Carousel, CarouselItem } from './Carousel';const items = Array.from({ length: 20 }).map((_, i) => ({
id: i,
src:
https://picsum.photos/500?idx=${i}
}));const App = () => (
items={items}
renderItem={({ item, isSnapPoint }) => (

)}
/>
);
export default App;
`Api
$3
#### Parameters
##### Options
`ts
interface SnapCarouselOptions {
// Horizontal or vertical carousel
readonly axis?: 'x' | 'y';
// Allows you to render pagination during SSR
readonly initialPages?: number[][];
}
`#### Return value
`ts
export interface SnapCarouselResult {
readonly pages: number[][];
readonly activePageIndex: number;
readonly snapPointIndexes: Set;
readonly hasPrevPage: boolean;
readonly hasNextPage: boolean;
readonly prev: (opts?: SnapCarouselGoToOptions) => void;
readonly next: (opts?: SnapCarouselGoToOptions) => void;
readonly goTo: (pageIndex: number, opts?: SnapCarouselGoToOptions) => void;
readonly refresh: () => void;
readonly scrollRef: (el: HTMLElement | null) => void;
}
``