Carousel UIs can be a great way to present lists of content without taking up too much precious real estate.
Today we'll build our own responsive carousel component with a little help from react-snap-carousel.
First up, create a basic Carousel component rendering a list of scrollable images.
export const Carousel = () => {
return (
<ul className="flex overflow-x-auto">
{Array.from({ length: 18 }).map((_, i) => (
<li key={i} className="flex-shrink-0">
<img
src={`https://picsum.photos/500?${i}`}
width="250"
height="250"
alt={`Item ${i}`}
/>
</li>
))}
</ul>
);
};
Okay let's face it, you've got yourself an MVP Carousel already so you could stop here 🙌. However, with a little bit of JavaScript we can progressively enhance our carousel with some nice-to-have features.
Firstly, let's add CSS scroll snap points so the user can swipe between each "page" of items.
import { useSnapCarousel } from "react-snap-carousel";
export const Carousel = () => {
const { scrollRef, snapPointIndexes } = useSnapCarousel();
return (
<ul className="flex overflow-x-auto snap-x snap-mandatory" ref={scrollRef}>
{Array.from({ length: 18 }).map((_, i) => (
<li
key={i}
className="flex-shrink-0"
style={{
scrollSnapAlign: snapPointIndexes.has(i) ? "start" : ""
}}
>
<img
src={`https://picsum.photos/500?${i}`}
width="250"
height="250"
alt={`Item ${i}`}
/>
</li>
))}
</ul>
);
};
Secondly, let's add some controls to navigate from one page to the next.
import { useSnapCarousel } from "react-snap-carousel";
export const Carousel = () => {
const { scrollRef, snapPointIndexes, next, prev } = useSnapCarousel();
return (
<>
<ul
className="flex overflow-x-auto snap-x snap-mandatory"
ref={scrollRef}
>
{Array.from({ length: 18 }).map((_, i) => (
<li
key={i}
className="flex-shrink-0"
style={{
scrollSnapAlign: snapPointIndexes.has(i) ? "start" : ""
}}
>
<img
src={`https://picsum.photos/500?${i}`}
width="250"
height="250"
alt={`Item ${i}`}
/>
</li>
))}
</ul>
<div className="flex justify-center space-x mt-2" aria-hidden>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mx-2"
onClick={() => prev()}
>
Previous
</button>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => next()}
>
Next
</button>
</div>
</>
);
};
Lastly, let's add some pagination controls to allow users to jump to a specific page.
import { useSnapCarousel } from "react-snap-carousel";
export const Carousel = () => {
const {
scrollRef,
snapPointIndexes,
next,
prev,
pages,
goTo
} = useSnapCarousel();
return (
<>
<ul
className="flex overflow-x-auto snap-x snap-mandatory"
ref={scrollRef}
>
{Array.from({ length: 18 }).map((_, i) => (
<li
key={i}
className="flex-shrink-0"
style={{
scrollSnapAlign: snapPointIndexes.has(i) ? "start" : ""
}}
>
<img
src={`https://picsum.photos/500?${i}`}
width="250"
height="250"
alt={`Item ${i}`}
/>
</li>
))}
</ul>
<div className="flex justify-center space-x mt-2" aria-hidden>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mx-2"
onClick={() => prev()}
>
Previous
</button>
{pages.map((_, i) => (
<button
key={i}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded mx-2"
onClick={() => goTo(i)}
>
{i + 1}
</button>
))}
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => next()}
>
Next
</button>
</div>
</>
);
};
Voilà, a fully functioning carousel component 🥳.
Final code on CodeSandbox.
TIP: Resize your browser to see how the CSS snap points and pagination dynamically update depending on how many items fit in the viewport.
If you want to take it further, you might want to consider the following:
- Highlight the active page using
activePageIndex
provided by React Snap Carousel. - Disable the previous / next buttons when on the first / last page respectively. Again using
activePageIndex
. - Hide the scrollbars with CSS.
- Make your carousel component reusable by accepting
items
as a prop.
You can find some inspiration by checking out more examples using React Snap Carousel here.
Top comments (0)