ScreenshotNeo

BlogHow-to

How to Add a Responsive Image Carousel to a React App

Build a responsive React image carousel with Swiper, accessible controls, responsive images, and practical loading and troubleshooting guidance.

By the ScreenshotNeo team4 October 20269 min read

A straightforward way to add a responsive image carousel to a React app is to use Swiper’s React integration. Install swiper, render one SwiperSlide per image, and use slidesPerView, spaceBetween, and breakpoints to adjust the layout for wider screens. The example below starts with one slide on narrow screens and shows more as space allows. Treat its breakpoint values as starting points: tune them to your content container and design.

This guide uses Swiper’s documented React components and API. A CSS-only carousel or another library is also possible; the right choice depends on how much built-in interaction you want versus how much control you need over markup and behavior. See the Swiper React documentation and Swiper API.

From your React project, install the package:

npm install swiper

Then create a component. Replace the sample image paths, descriptions, and dimensions with your own assets. Each image’s width and height should reflect its actual intrinsic dimensions.

import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';

const images = [
  { src: '/images/coast.jpg', alt: 'Rocky coast at sunset', width: 1200, height: 800 },
  { src: '/images/forest.jpg', alt: 'Sunlight through a forest', width: 1200, height: 800 },
  { src: '/images/city.jpg', alt: 'City buildings at dusk', width: 1200, height: 800 },
];

export function ImageCarousel() {
  return (
    <Swiper
      slidesPerView={1}
      spaceBetween={12}
      breakpoints={{
        640: { slidesPerView: 2, spaceBetween: 16 },
        1024: { slidesPerView: 3, spaceBetween: 24 },
      }}
    >
      {images.map((image) => (
        <SwiperSlide key={image.src}>
          <img
            src={image.src}
            alt={image.alt}
            width={image.width}
            height={image.height}
            loading="lazy"
            style={{ display: 'block', width: '100%', height: 'auto' }}
          />
        </SwiperSlide>
      ))}
    </Swiper>
  );
}

The code is JSX. In an HTML article, angle brackets inside a code block are escaped so the snippet displays as code; in a .jsx file, use ordinary < and > characters. The important pieces are the stable slide key, the base stylesheet, and a mobile-friendly default.

2. Choose responsive slide counts and spacing

Swiper breakpoint keys are minimum viewport widths by default. In the example, the base configuration applies below 640 CSS pixels, the 640 configuration applies from 640 pixels, and the 1024 configuration applies from 1024 pixels. These are example thresholds, not universal device categories.

  • slidesPerView: how many slides are visible in the viewport. Use a number for a fixed count. Swiper also supports 'auto', which requires slide widths to be defined in CSS; check the API for the exact behavior and constraints of the installed version.
  • spaceBetween: the gap between slides, in pixels.
  • breakpoints: an object mapping minimum widths to supported parameter overrides. Increase the visible count only when the carousel’s container has enough room.

Breakpoints are best for layout changes such as visible slide count and spacing. Swiper documents that options which alter core logic, including loop and effect, do not work as breakpoint overrides. Check the API’s supported breakpoint parameters before putting an option there.

If the component sits in a resizable panel or a grid column, viewport width may not match the carousel’s available width. Swiper documents container-based breakpoints as beta; check the current API and installed version before relying on them. Otherwise, choose viewport breakpoints that fit the actual page layout.

3. Add image sizing, responsive sources, and loading behavior

Image handling affects both visual stability and the amount of data a carousel loads. React’s image reference documents srcSet, sizes, loading behavior, and alternative text.

  • Reserve space: provide the image’s true intrinsic width and height, or reserve an intentional aspect ratio in CSS. This helps the browser lay out the slide before the image finishes loading.
  • Choose crop behavior deliberately: if gallery cards should align, set a consistent aspect ratio and use object-fit: cover. If viewers must see the entire image, use contain instead. These are CSS choices, independent of Swiper.
  • Offer responsive image files when available: use srcSet and sizes when you have appropriately generated variants. The browser can select a candidate based on the image’s rendered size and display context.
  • Describe the image: write meaningful alt text when the image conveys information. For a purely decorative image, use alt="".
  • Lazy-load offscreen slides selectively: native loading="lazy" can defer images outside the initial view. Avoid blindly applying it to the first, prominent image above the fold; eager loading may be more appropriate there.

Swiper’s current API describes native browser lazy loading from version 9 onward and notes limitations for its own lazy-preload option in React and Vue. Prefer the browser’s image attributes for this basic case, then consult the API for the version in your project if you need Swiper-specific lazy behavior.

4. Add keyboard-operable navigation controls

Touch swiping is useful, but it should not be the only way to move through slides. Swiper React supports Navigation and Pagination modules; import only the modules you need, pass them through modules, and include their CSS.

import { useRef } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';

export function Gallery({ images }) {
  const previousRef = useRef(null);
  const nextRef = useRef(null);

  return (
    <section aria-label="Featured images">
      <button ref={previousRef} type="button" aria-label="Previous images">
        Previous
      </button>
      <button ref={nextRef} type="button" aria-label="Next images">
        Next
      </button>
      <Swiper
        modules={[Navigation, Pagination]}
        navigation={{ prevEl: previousRef.current, nextEl: nextRef.current }}
        pagination={{ clickable: true }}
        onBeforeInit={(swiper) => {
          swiper.params.navigation.prevEl = previousRef.current;
          swiper.params.navigation.nextEl = nextRef.current;
        }}
      >
        {images.map((image) => (
          <SwiperSlide key={image.src}>
            <img src={image.src} alt={image.alt} width={image.width} height={image.height} />
          </SwiperSlide>
        ))}
      </Swiper>
    </section>
  );
}

The module example shows the documented integration pattern, but custom element references can be sensitive to initialization timing and library version. If controls do not attach, use Swiper’s documented Navigation configuration for your installed version or use Swiper’s built-in navigation elements, then verify the controls with keyboard and pointer input.

Give the carousel an accessible name, use buttons with discernible labels, keep a visible focus indicator, and make the current position understandable. W3C’s carousel tutorial covers structure, functionality, and styling; the ARIA Authoring Practices carousel pattern describes previous/next and rotation controls. The styling tutorial recommends 44 × 44 CSS pixel targets under WCAG 2.5.5, a Level AAA criterion; that is useful guidance, not a universal minimum for every conformance level.

For most image galleries, user-controlled navigation is the simpler starting point. Autoplay can move content before a person has finished viewing it, so add it only when the experience needs it and implement a clear way to pause or stop it.

W3C WAI states: “Users must be able to pause carousel movement because it can be too fast or distracting, making text hard to read.” See the WAI carousel tutorial. The WAI APG pattern also says automatic rotation stops when keyboard focus enters the carousel and while the pointer hovers, and it must not restart after focus leaves unless the user explicitly starts it again.

  • Provide a rotation control whose label and state make the action clear.
  • Stop rotation on focus and hover; do not silently resume after focus leaves.
  • Keep focus on the activated previous/next control rather than moving it into slide content.
  • Announce user-triggered changes politely if needed, without sending disruptive announcements for every automatic advance.
  • Test the exact behavior of the Swiper version and modules you use; an accessibility module alone does not guarantee that the assembled carousel meets these requirements.

Swiper offers a React adapter and documented modules for common carousel behavior. A custom component can give you more control over markup and interaction and may avoid a dependency, but you must build and maintain sizing, touch behavior, controls, focus handling, and announcements yourself. Compare the interaction requirements, dependency and bundle constraints, and maintenance needs of your app. There is no universal winner, and this guide does not claim a performance comparison or runtime test.

7. Troubleshoot common problems

Symptom Likely cause What to check
Slides appear stacked or unstyled The base Swiper stylesheet is missing or not included in the app’s build. Import swiper/css and confirm your bundler processes package CSS.
The carousel shows the wrong number of slides A breakpoint is based on viewport width, while the component’s container is narrower, or the threshold has not been reached. Inspect the viewport width and the carousel’s actual available space; adjust the thresholds and counts to fit the design.
Navigation buttons do nothing The Navigation module or stylesheet is missing, or custom button references were not available when Swiper initialized. Import and pass the module, verify the CSS, and follow the integration documented for your Swiper version.
Images stretch, crop unexpectedly, or change slide height Dimensions, aspect ratio, or object-fit do not match the intended presentation. Use true intrinsic dimensions, choose cover or contain deliberately, and reserve a consistent ratio only if the design calls for it.
Images load slowly or consume more data than expected Large originals are used at every size, or offscreen images are loaded eagerly. Provide suitable srcSet/sizes variants where available and lazy-load offscreen images; treat the initial prominent image separately.
React warns about list keys A slide has no stable key, or an array index is being used for content that can reorder. Use a stable image identifier such as a unique source path or record ID.
Autoplay keeps moving while someone reads Rotation was added without stop behavior for focus and hover or without a user-facing pause control. Implement the WAI guidance and test keyboard, pointer, and restart behavior.

8. Performance, reliability, and cost considerations

There is no benchmark in the sources used for this guide, so choose image sizes and carousel behavior from your app’s content and measure them in your own environment if performance is a concern. Responsive image candidates can avoid sending an unnecessarily large source to a smaller display; lazy loading can defer offscreen images; intrinsic dimensions help reserve layout space. Keep the initial visible image discoverable and avoid loading a large set of originals when smaller variants meet the design.

Reliability comes from handling ordinary image failures and layout changes: provide an appropriate fallback or alt text when an image cannot load, keep controls usable at narrow widths, and verify behavior when the component is rendered in its real page container. Pin or review the Swiper version used by the project and check its current API when changing module or breakpoint configuration.

The library approach adds a package dependency and the code and maintenance associated with the features you enable. A custom implementation trades that dependency for your own implementation and upkeep of interaction details. No measured bundle, speed, or cost comparison is asserted here.

9. Or skip the browser setup

If your task is to capture a page as an image while building or documenting a site, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for options and request details.

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

Cookie banners are accepted and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card.

10. Frequently asked questions

Do I need to use Swiper?

No. Swiper is one documented option. A CSS-only or custom React carousel can fit apps that need different markup or interaction, provided you implement keyboard access and other required behavior.

Can I show part of the next slide?

Yes. You can tune slide sizing and the visible area to suggest more content, but choose a configuration supported by the Swiper version you use and check that controls and focus remain clear.

Should every slide image be lazy-loaded?

No. Lazy loading is most useful for offscreen images. Consider eager loading for the initial prominent image so it can be discovered promptly.

Can I change effects at a breakpoint?

Swiper documents that logic-changing options such as effect and loop are not supported as breakpoint changes. Use the API’s list of supported breakpoint parameters.