Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The most reliable way to build an image carousel is to start with a semantic list of slides, add responsive CSS, and then add JavaScript that changes visibility without taking keyboard focus. Use real <button> elements for controls, announce slide changes to assistive technology, and make automatic rotation optional with a clearly labeled pause control. The complete framework-neutral example below follows that order.

Decide whether a carousel is the right pattern

A carousel (also called a slideshow or slider) displays one or more items at a time. It can present a sequence of related images, but it is not automatically better than a static gallery. The W3C Web Accessibility Initiative cautions that carousel content can be difficult to discover. If visitors need to compare images, a visible grid may be clearer. Use a carousel when showing one featured item at a time supports the task; otherwise, keep several images visible.

Before writing code, define what every slide communicates and whether it contains a caption, link, or action. Informative images need meaningful alternative text. A decorative image should use an empty alt value so a screen reader does not hear a redundant description.

Choose an implementation approach

Approach Accessibility and control Complexity and dependencies Best fit
Plain HTML, CSS and JavaScript You define semantics, keyboard behavior, announcements and rotation yourself. No dependency; requires careful testing. A small site or a team wanting complete control.
Existing framework component Can provide a tested pattern, but you must verify its labels, focus behavior, slide visibility and rotation controls. Less custom code, but tied to the framework and its configuration. A site already committed to that framework.
Direct next/previous controls Simple to understand and keyboard-operable when implemented with buttons. Lowest interaction complexity. A short sequence where users move sequentially.
Direct slide selection Requires labeled buttons and a visible current-state indication. More controls and state to maintain. Longer sequences where users need to jump to a known image.

These are implementation choices, not a claim that one library is universally superior. W3C’s Design System describes a content slider as an interactive component that needs semantic HTML, considered JavaScript and ARIA.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

1. Create semantic slide markup

Put the carousel in a labeled region. A visible heading is preferable; connect the region to it with aria-labelledby. Keep slides in a list so the content remains meaningful if styling or script is unavailable. The following markup includes previous and next buttons, direct slide selectors, a status live region, and a pause button. The first slide is visible; the others are hidden with the hidden attribute.

<section class="carousel" aria-labelledby="featured-title">
  <h2 id="featured-title">Featured landscapes</h2>

  <div class="carousel__viewport">
    <ul class="carousel__slides">
      <li class="carousel__slide" data-index="0">
        <figure>
          <img src="images/mountain.jpg" alt="Snow-covered mountain above a blue lake">
          <figcaption>Mountain lake at sunrise</figcaption>
        </figure>
      </li>
      <li class="carousel__slide" data-index="1" hidden>
        <figure>
          <img src="images/forest.jpg" alt="Tall green trees beside a mossy path">
          <figcaption>A path through the forest</figcaption>
        </figure>
      </li>
      <li class="carousel__slide" data-index="2" hidden>
        <figure>
          <img src="images/coast.jpg" alt="Rocky coast with waves under a cloudy sky">
          <figcaption>Waves on the coast</figcaption>
        </figure>
      </li>
    </ul>
  </div>

  <div class="carousel__controls">
    <button type="button" class="carousel__previous" aria-label="Previous slide">‹</button>
    <div class="carousel__dots" aria-label="Choose a slide">
      <button type="button" aria-label="Show slide 1" aria-current="true">1</button>
      <button type="button" aria-label="Show slide 2" aria-current="false">2</button>
      <button type="button" aria-label="Show slide 3" aria-current="false">3</button>
    </div>
    <button type="button" class="carousel__next" aria-label="Next slide">›</button>
    <button type="button" class="carousel__toggle" aria-pressed="false">Pause</button>
  </div>

  <p class="carousel__status" aria-live="polite" aria-atomic="true">Slide 1 of 3</p>
</section>

Buttons have an explicit type so they do not accidentally submit a surrounding form. The selector buttons expose which slide they choose and use aria-current to identify the current one. Do not move focus when a slide changes; the person can continue from the control they activated.

2. Add responsive, readable CSS

Size the viewport with the available width, preserve each image’s proportions, and choose a fixed crop only when it suits the content. Controls must remain visible over light and dark portions of an image. A contrasting background, border, shadow or whitespace helps users locate them.

.carousel {
  max-width: 60rem;
  margin-inline: auto;
  padding: 1rem;
}

.carousel__viewport {
  overflow: hidden;
  border-radius: .5rem;
  background: #111;
}

.carousel__slides {
  list-style: none;
  margin: 0;
  padding: 0;
}

.carousel__slide[hidden] { display: none; }
.carousel__slide figure { margin: 0; }
.carousel__slide img {
  display: block;
  width: 100%;
  height: auto;
}

.carousel__slide figcaption {
  padding: .75rem 1rem;
  color: #fff;
  background: #111;
}

.carousel__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  margin-top: .75rem;
}

.carousel button {
  min-width: 44px;
  min-height: 44px;
  border: 2px solid #222;
  border-radius: .35rem;
  background: #fff;
  color: #111;
  cursor: pointer;
}

.carousel button:focus-visible {
  outline: 3px solid #0b63ce;
  outline-offset: 3px;
}

.carousel__dots { display: flex; gap: .5rem; }
.carousel__dots button[aria-current="true"] {
  background: #222;
  color: #fff;
}

.carousel__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .carousel * { scroll-behavior: auto; transition: none; }
}

WAI’s styling guidance recommends at least 44 × 44 CSS pixels for non-inline buttons and links. That recommendation concerns the activation target, not the image dimensions. Keep enough spacing between controls to reduce accidental taps. If controls overlay the image, test them against every image rather than relying on one background.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Add keyboard-safe JavaScript behavior

The script below maintains one current index, toggles hidden, updates the current selector, and writes a polite announcement. Previous, next and selector actions all use the same function, so state cannot drift between controls.

const root = document.querySelector('.carousel');
const slides = [...root.querySelectorAll('.carousel__slide')];
const dots = [...root.querySelectorAll('.carousel__dots button')];
const previous = root.querySelector('.carousel__previous');
const next = root.querySelector('.carousel__next');
const toggle = root.querySelector('.carousel__toggle');
const status = root.querySelector('.carousel__status');

let current = 0;
let timer = null;
const interval = 6000;

function showSlide(index, announce = true) {
  current = (index + slides.length) % slides.length;
  slides.forEach((slide, i) => {
    slide.hidden = i !== current;
  });
  dots.forEach((dot, i) => {
    dot.setAttribute('aria-current', String(i === current));
  });
  if (announce) status.textContent = `Slide ${current + 1} of ${slides.length}`;
}

function startRotation() {
  if (timer === null) timer = setInterval(() => showSlide(current + 1), interval);
  toggle.textContent = 'Pause';
  toggle.setAttribute('aria-pressed', 'false');
}

function stopRotation() {
  clearInterval(timer);
  timer = null;
  toggle.textContent = 'Play';
  toggle.setAttribute('aria-pressed', 'true');
}

previous.addEventListener('click', () => showSlide(current - 1));
next.addEventListener('click', () => showSlide(current + 1));
dots.forEach((dot, i) => dot.addEventListener('click', () => showSlide(i)));
toggle.addEventListener('click', () => timer === null ? startRotation() : stopRotation());

// Optional: begin only if motion has not been disabled by the user.
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) startRotation();
else stopRotation();

showSlide(0, false);

All functionality, including navigating between carousel items, must be operable by keyboard, as stated by the W3C Web Accessibility Initiative’s Carousels Tutorial. Native buttons provide Tab focus and Enter/Space activation without custom key handlers. The script never forces focus into the slide, so a keyboard user is not unexpectedly relocated.

Automatic rotation: use it only with user control

Moving content can distract people or make text difficult to read. If you rotate automatically, provide a pause/stop control that is available before or alongside the moving content, make its label describe its current action, and stop rotation when the user chooses a slide. The example’s pause button remains available and honors prefers-reduced-motion. You can omit rotation entirely by removing the timer and toggle; manual navigation is often the simpler choice.

Do not use a hover-only pause mechanism: it is unavailable to keyboard and touch users. Consider pausing while the carousel is focused or hovered as an additional convenience, but keep an explicit button as the primary control.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test the finished carousel

  • With only a keyboard, Tab to every button, activate each one with Enter and Space, and verify that focus stays predictable.
  • Check that each control has an understandable accessible name, and that the selected slide is exposed through aria-current.
  • With a screen reader, confirm that changing slides produces a concise “Slide n of total” announcement and does not read every hidden slide.
  • Turn off JavaScript and confirm the slide list still contains meaningful content rather than an empty region.
  • Resize from a narrow phone viewport to a wide monitor. Images should not be distorted, and controls should not overlap captions.
  • Test controls over the lightest and darkest images, at high zoom and with increased text spacing.
  • Use a touch device: targets should be comfortably separated and at least 44 × 44 CSS pixels.
  • Enable reduced motion and verify that automatic rotation is disabled.

Common failures and fixes

Every image appears at once

Usually the script did not load, the selector is wrong, or the hidden attribute was overridden by CSS. Confirm the script runs after the markup exists and keep .carousel__slide[hidden] { display: none; } in the stylesheet.

Next and previous wrap incorrectly

Use modulo arithmetic as in (index + slides.length) % slides.length. Adding the slide count before the modulo also handles a negative previous index.

Screen readers announce stale slide numbers

Update the live-region text in the same function that changes visibility. Set aria-atomic="true" so the complete short message is announced.

Controls disappear on some images

Increase contrast with an opaque control background, border and focus outline. Do not depend on color alone to indicate the current selector.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Rotation continues after a user action

Stop the timer when a person selects a dot or uses previous/next, or make the pause state explicit. A carousel that ignores user control is difficult to operate.

Images load slowly

Use appropriately sized, compressed images and reserve the carousel’s space to avoid layout shifts. Lazy-load off-screen images only when that does not delay the first meaningful slide; test on a slow connection rather than assuming it helps.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need screenshots of a finished page for documentation, previews or automated checks, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF. It accepts cookie/consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.

Use the API documentation at https://screenshotneo.com/docs/ for all options, including full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets, custom viewport and retina scale, PDF paper settings, custom CSS or JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparency, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data and the OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get started.

Further accessibility references

For the underlying pattern, see WAI’s carousel structure, functionality and styling guidance, plus the ARIA Authoring Practices carousel pattern. The W3C Design System’s content slider example discusses semantic HTML, JavaScript and ARIA and notes accessibility testing in 2021 by the Digital Accessibility Centre.

Frequently Asked Questions

Should every website use an image carousel?

No. Use a static gallery when comparison or discoverability matters more than showing one featured item at a time.

Can I build a carousel without a JavaScript framework?

Yes. Semantic HTML, responsive CSS and a small script are sufficient for previous, next and direct-selection behavior.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is automatic rotation required?

No. Manual navigation is valid and often easier to make predictable. If you rotate, provide a prominent pause or stop control and respect reduced-motion preferences.

Quick Recap

SaleBestseller No. 1
HTML and CSS: Design and Build Websites
HTML and CSS: Design and Build Websites
HTML CSS Design and Build Web Sites; Comes with secure packaging; It can be a gift option
$15.75
SaleBestseller No. 3
SaleBestseller No. 4
Web Design with HTML, CSS, JavaScript and jQuery Set
Web Design with HTML, CSS, JavaScript and jQuery Set
Brand: Wiley; Set of 2 Volumes
$35.05

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.