Carousel
Implement a basic image carousel with 'next' and 'previous' buttons to cycle through images.
Solution
Horizontal Scroll
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Horizontal Buttons
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Horizontal Snap
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Explanation
The setup
I've made 3 different flavours for you to learn.
Each one consists of a list
with list items
placed within a container div
.
The trick
Most of this work can be achieved with CSS
alone. You need to set the container's overflow-x
property to scroll
.
The buttons are linked via a ref
. When they are actioned, prevButton
and nextButton
will add or subtract an amount to the scrollLeft
property.
The value you add is determined by the total width of each section including borders.
Extras
Add scroll-behaviour: smooth
to make sure the transitions don't jump.
And add scroll-snap-type: x mandatory
if you want the sections to perfectly snap into place when scrolling.
Code
import styles from "./Solution.module.css";
import { useRef } from "react";
const Solution = () => {
const listRef = useRef<HTMLUListElement>(null);
const prevButton = () => {
if (listRef.current) listRef.current.scrollLeft -= 216;
};
const nextButton = () => {
if (listRef.current) listRef.current.scrollLeft += 216;
};
return (
<>
<div className={styles.container}>
<h2>Horizontal Scroll</h2>
<ul className={styles.list1}>
<li className={styles.mockImage}>1</li>
<li className={styles.mockImage}>2</li>
<li className={styles.mockImage}>3</li>
<li className={styles.mockImage}>4</li>
<li className={styles.mockImage}>5</li>
<li className={styles.mockImage}>6</li>
<li className={styles.mockImage}>7</li>
<li className={styles.mockImage}>8</li>
<li className={styles.mockImage}>9</li>
</ul>
</div>
<div className={styles.container}>
<h2>Horizontal Buttons</h2>
<ul ref={listRef} className={styles.list2}>
<li className={styles.mockImage}>1</li>
<li className={styles.mockImage}>2</li>
<li className={styles.mockImage}>3</li>
<li className={styles.mockImage}>4</li>
<li className={styles.mockImage}>5</li>
<li className={styles.mockImage}>6</li>
<li className={styles.mockImage}>7</li>
<li className={styles.mockImage}>8</li>
<li className={styles.mockImage}>9</li>
</ul>
<div className={styles.buttonContainer}>
<button
aria-label="Previous Image"
onClick={prevButton}
className={styles.buttonPrev}
>
Prev
</button>
<button
aria-label="Next Image"
onClick={nextButton}
className={styles.buttonNext}
>
Next
</button>
</div>
</div>
<div className={styles.container}>
<h2>Horizontal Snap</h2>
<ul className={styles.list3}>
<li className={styles.mockImage}>1</li>
<li className={styles.mockImage}>2</li>
<li className={styles.mockImage}>3</li>
<li className={styles.mockImage}>4</li>
<li className={styles.mockImage}>5</li>
<li className={styles.mockImage}>6</li>
<li className={styles.mockImage}>7</li>
<li className={styles.mockImage}>8</li>
<li className={styles.mockImage}>9</li>
</ul>
</div>
</>
);
};
export default Solution;
Styling
.list1 {
border: 2px solid black;
border-radius: 5px;
padding: 1rem;
display: flex;
gap: 1rem;
width: 550px;
overflow-x: scroll;
background-color: white;
}
.list2 {
border: 2px solid black;
border-radius: 5px;
padding: 1rem;
display: flex;
gap: 1rem;
width: 556px;
overflow-x: hidden;
scroll-behavior: smooth;
background-color: white;
}
.list3 {
border: 2px solid black;
border-radius: 5px;
/* want padding same as gap */
padding: 16px;
gap: 16px;
display: flex;
width: 232px; /* 200px width plus 16px gap both sides */
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
background-color: white;
}
.list3 li {
scroll-snap-align: center;
flex-shrink: 0;
width: 200px; /* your item width */
}
.buttonContainer {
width: 550px;
padding: 20px;
display: flex;
gap: 15px;
justify-content: flex-start;
}
.buttonContainer button {
padding: 5px;
border-radius: 5px;
cursor: pointer;
}
.container {
margin: 20px 0;
}
.mockImage {
display: grid;
place-content: center;
font-size: 3rem;
height: 300px;
min-width: 200px;
background-color: aquamarine;
border: 1px solid black;
}