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;
}

Links