Image Slider Carousel Using HTML, CSS and JavaScript

Beginner 2 views Oct 11, 2026

Sliders are everywhere: hero banners, product galleries, testimonials, onboarding screens. Many people reach for a heavy plugin the moment they need one, but a good slider is only about sixty lines of JavaScript. In this tutorial you will create an Image Slider Carousel that has arrows, dots, autoplay, keyboard control and touch swipe.

To keep the project self-contained, the slides use CSS gradients instead of photos, so you can run it without any files. Replacing them with real images later is a one-line change, and we show you how in the customisation section.

What You'll Learn

By the end of this tutorial you will know:

  • How a slider really works: a wide row of slides moved with translateX()
  • How to calculate the next and previous slide with the modulo operator
  • How to build dot indicators from JavaScript
  • How to start and stop autoplay with setInterval()
  • How to react to keyboard arrow keys
  • How to detect a swipe using touchstart and touchend

Features of This Image Slider

  • Previous and next arrows
  • Clickable dots showing the current slide
  • Autoplay every 4 seconds that pauses when you hover
  • Left and right arrow keys work too
  • Swipe left or right on touch screens
  • Loops forever in both directions
  • Fully responsive

Prerequisites

You only need the basics: how to write a simple HTML page, a few CSS rules, and what a JavaScript variable and function are. Along with that, have a code editor such as Visual Studio Code and any modern browser ready. No frameworks, no build tools and no installs.

Project Structure

Create a new folder called image-slider and add three empty files inside it:

PLAINTEXT
image-slider/
├── index.html   → the page structure
├── style.css    → the look and layout
└── script.js    → the behaviour

Step 1: Create the HTML Structure

Create index.html. The structure is a window (.slider) that hides overflow, containing one long track (.track) with all slides side by side. Arrows are normal buttons and the dots container is filled by JavaScript.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Image Slider</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <section class="slider" id="slider" aria-label="Image slider">
    <div class="track" id="track">
      <div class="slide s1"><h2>Learn HTML</h2><p>Build the structure of every web page</p></div>
      <div class="slide s2"><h2>Style with CSS</h2><p>Colours, layouts and animation</p></div>
      <div class="slide s3"><h2>Code in JavaScript</h2><p>Make your pages come alive</p></div>
      <div class="slide s4"><h2>Ship a Project</h2><p>Practise by building real things</p></div>
    </div>

    <button class="arrow prev" id="prev" aria-label="Previous slide">❮</button>
    <button class="arrow next" id="next" aria-label="Next slide">❯</button>

    <div class="dots" id="dots"></div>
  </section>

  <script src="script.js"></script>
</body>
</html>

Step 2: Add the CSS Styling

Create style.css. Each slide is exactly 100% of the slider width and does not shrink (flex: 0 0 100%). The track uses display: flex, so the slides line up in a row. Moving the track to the left by 100% × index reveals the next slide.

CSS
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Segoe UI", Arial, sans-serif;
  background: #eef1f8;
  color: #1f2937;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.slider {
  position: relative;
  width: 100%;
  max-width: 760px;
  height: 340px;
  overflow: hidden;
  border-radius: 18px;
  box-shadow: 0 14px 36px rgba(31, 41, 55, 0.22);
}

.track {
  display: flex;
  height: 100%;
  transition: transform 0.55s ease;
}

.slide {
  flex: 0 0 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: #fff;
  padding: 20px;
}

.slide h2 {
  font-size: 2.2rem;
  margin-bottom: 8px;
}

.slide p {
  font-size: 1.1rem;
  opacity: 0.9;
}

.s1 { background: linear-gradient(135deg, #f97316, #ef4444); }
.s2 { background: linear-gradient(135deg, #3b82f6, #6366f1); }
.s3 { background: linear-gradient(135deg, #10b981, #0ea5e9); }
.s4 { background: linear-gradient(135deg, #8b5cf6, #ec4899); }

.arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  font-size: 1.1rem;
  cursor: pointer;
  transition: background 0.2s;
}

.arrow:hover {
  background: #fff;
}

.prev { left: 14px; }
.next { right: 14px; }

.dots {
  position: absolute;
  bottom: 14px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 8px;
}

.dot {
  width: 11px;
  height: 11px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  padding: 0;
}

.dot.active {
  background: #fff;
  transform: scale(1.25);
}

@media (max-width: 520px) {
  .slider { height: 260px; }
  .slide h2 { font-size: 1.5rem; }
}

Step 3: Add the JavaScript

Create script.js. We only need to remember one number, current, which is the index of the visible slide. Every action (arrow, dot, key, swipe, autoplay) just changes that number and calls goTo().

JAVASCRIPT
// ----- Elements -----
const slider = document.getElementById('slider');
const track = document.getElementById('track');
const slides = document.querySelectorAll('.slide');
const dotsEl = document.getElementById('dots');

let current = 0;           // index of the slide we are showing
let autoplayId = null;
const total = slides.length;

// ----- Build one dot for each slide -----
slides.forEach(function (slide, index) {
  const dot = document.createElement('button');
  dot.className = 'dot';
  dot.setAttribute('aria-label', 'Go to slide ' + (index + 1));
  dot.addEventListener('click', function () {
    goTo(index);
    restartAutoplay();
  });
  dotsEl.appendChild(dot);
});

const dots = document.querySelectorAll('.dot');

// ----- Show a specific slide -----
function goTo(index) {
  // The modulo trick makes the slider loop: -1 becomes the last slide, total becomes 0
  current = (index + total) % total;
  track.style.transform = 'translateX(-' + current * 100 + '%)';

  dots.forEach(function (dot, i) {
    dot.classList.toggle('active', i === current);
  });
}

function next() { goTo(current + 1); }
function prev() { goTo(current - 1); }

// ----- Arrows -----
document.getElementById('next').addEventListener('click', function () {
  next();
  restartAutoplay();
});
document.getElementById('prev').addEventListener('click', function () {
  prev();
  restartAutoplay();
});

// ----- Keyboard -----
document.addEventListener('keydown', function (e) {
  if (e.key === 'ArrowRight') { next(); restartAutoplay(); }
  if (e.key === 'ArrowLeft') { prev(); restartAutoplay(); }
});

// ----- Autoplay -----
function startAutoplay() {
  stopAutoplay();
  autoplayId = setInterval(next, 4000);
}

function stopAutoplay() {
  clearInterval(autoplayId);
  autoplayId = null;
}

function restartAutoplay() {
  startAutoplay();
}

slider.addEventListener('mouseenter', stopAutoplay);
slider.addEventListener('mouseleave', startAutoplay);

// ----- Touch swipe -----
let touchStartX = 0;

slider.addEventListener('touchstart', function (e) {
  touchStartX = e.changedTouches[0].clientX;
  stopAutoplay();
}, { passive: true });

slider.addEventListener('touchend', function (e) {
  const distance = e.changedTouches[0].clientX - touchStartX;

  // Only count it as a swipe if the finger moved at least 50px
  if (distance < -50) next();
  if (distance > 50) prev();

  startAutoplay();
}, { passive: true });

// ----- Start -----
goTo(0);
startAutoplay();

How This Image Slider Carousel Works

  • The track is as wide as all slides together. Moving it by -100% shows slide two, -200% shows slide three, and so on. The CSS transition makes that movement smooth.
  • goTo() is the only function that changes the slide. Because everything calls it, the dots, arrows, keys and swipe can never disagree about which slide is current.
  • Looping uses (index + total) % total. Adding total first stops negative numbers from breaking the modulo.
  • Dots are generated from the number of slides, so adding a fifth slide in the HTML automatically adds a fifth dot.
  • Autoplay pauses on mouseenter and resumes on mouseleave. After any manual action we restart the timer so the slide does not jump a moment after you click.
  • Swipe compares where the finger touched and where it lifted. A 50 pixel threshold prevents accidental taps from changing slides.

Customization Guide

1. Use real photos

Replace the text inside each .slide with .... Always write a meaningful alt text for each image.

2. Change the speed

Edit 4000 in startAutoplay() for the delay between slides and 0.55s in the CSS for the slide animation.

3. Add a fade effect

Remove the track movement and give each slide position: absolute with an opacity transition. Then toggle an active class instead of using translateX.

Future Enhancements

Once the basic version works, here are some ideas to practise with:

  • Add a pause button for accessibility
  • Show thumbnails below the main image
  • Add captions that animate in
  • Support dragging with a mouse
  • Preload images lazily for speed

Troubleshooting Guide

All slides show stacked below each other

The track needs display: flex and every slide needs flex: 0 0 100%. Without them the slides behave like normal blocks.

The slider jumps instead of sliding

Check that the transition: transform rule is on .track, not on .slide.

Swipe also scrolls the page

Keep the touch listeners passive as shown, and make sure the slider has no vertical overflow. For horizontal-only gestures you can add touch-action: pan-y to the slider.

Related Mini Projects

Want more practice? Try these beginner projects next:

Conclusion

You have built a slider from scratch and understand exactly how it works, which means you can fix it, restyle it and extend it without depending on a plugin. The single source of truth idea, one variable and one function that updates everything, is also the foundation of modern frameworks. Swap in your own images and it is ready for a real website.

Technologies Used

HTML5 CSS3 JavaScript Touch Events

Live Output

See the project in action below. This is the live preview of the code you just learned.

Output Preview