Experience the powerful AI writing right inside WordPress
Show stunning before-and-after transformations with image sliders.
Improve user engagement by showing estimated reading time.
Written by Mahmuda Akter Isha
Showcase Designs Using Before After Slider.
A WordPress slider with three images can be created using HTML, CSS, and JavaScript without a plugin. The carousel shows three images on desktop, two on tablets, and one on mobile, with navigation buttons, autoplay, swipe support, and keyboard controls. You can test the code in CodePen and then add it to WordPress using a Custom HTML block and separately loaded CSS and JavaScript.
A WordPress slider with three images is an effective way to display products, portfolio projects, testimonials, blog posts, team members, or photographs without using too much page space.
In this tutorial, you will create a responsive multiple-image slider using HTML, CSS, and vanilla JavaScript. The carousel displays three images at a time on desktop screens, two images on tablets, and one image on mobile devices.
You can test the complete slider in CodePen before adding it to your WordPress website. The finished slider includes previous and next buttons, navigation dots, autoplay controls, swipe gestures, keyboard navigation, responsive breakpoints, and support for multiple sliders on the same page.
No external JavaScript carousel library is required.
To create a WordPress slider with three images, place the images inside a horizontal flexbox track, set each slide to occupy one-third of the visible area on desktop, and use JavaScript to move the track when visitors select the previous or next controls.
Use responsive CSS breakpoints to change the number of visible images:
The HTML creates the carousel structure, CSS controls the layout and animation, and JavaScript manages navigation, autoplay, swipe gestures, responsive calculations, and accessibility states.
The slider in this tutorial provides:
The terms “slider” and “carousel” are often used interchangeably, but they can describe slightly different layouts.
A traditional image slider commonly displays one large image at a time. A carousel can display several items within the same visible area and move them horizontally.
Because the example in this tutorial displays three images simultaneously, it is technically a multiple-item carousel. However, many WordPress users search for this feature using phrases such as:
This tutorial uses both “slider” and “carousel” to make the instructions easier to understand.
A responsive three-image slider works well when visitors need to browse several related items without leaving the current section.
Show featured products, product categories, new arrivals, or recommended items within an online store.
Present landscapes, event photographs, design projects, or creative work while keeping the page organized.
Display recent posts, related articles, popular resources, or category-based content.
Show employee photographs, roles, names, and short descriptions in a compact layout.
Present customer stories or reviews as individual cards.
Display several services with an image, heading, description, and call-to-action button.
Show houses, apartments, interiors, or commercial properties in a responsive carousel.
CodePen separates front-end code into HTML, CSS, and JavaScript panels. Paste each of the following code sections into its matching panel.
The example contains six images. Six or more slides are recommended because displaying only three images in a three-items-per-view layout would leave nothing additional to reveal on desktop.
Paste the following code into the HTML panel in CodePen:
<section class="three-image-carousel" aria-label="Featured image gallery" data-autoplay="true" data-interval="4500" > <div class="carousel__viewport" tabindex="0" aria-label="Use the left and right arrow keys to navigate" > <ul class="carousel__track"> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1015/900/600" alt="Sample landscape image showing mountains and water" width="900" height="600" decoding="async" > <figcaption>Mountain Landscape</figcaption> </figure> </li> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1016/900/600" alt="Sample outdoor landscape photograph" width="900" height="600" decoding="async" > <figcaption>Outdoor Adventure</figcaption> </figure> </li> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1025/900/600" alt="Sample animal photograph" width="900" height="600" decoding="async" > <figcaption>Wildlife Photography</figcaption> </figure> </li> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1035/900/600" alt="Sample scenic nature photograph" width="900" height="600" loading="lazy" decoding="async" > <figcaption>Natural Scenery</figcaption> </figure> </li> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1043/900/600" alt="Sample landscape with a calm natural setting" width="900" height="600" loading="lazy" decoding="async" > <figcaption>Peaceful View</figcaption> </figure> </li> <li class="carousel__slide"> <figure> <img src="https://picsum.photos/id/1050/900/600" alt="Sample travel and nature photograph" width="900" height="600" loading="lazy" decoding="async" > <figcaption>Travel Photography</figcaption> </figure> </li> </ul> <button class="carousel__button carousel__button--prev" type="button" aria-label="Show previous images" > ❮ </button> <button class="carousel__button carousel__button--next" type="button" aria-label="Show next images" > ❯ </button> </div> <div class="carousel__footer"> <div class="carousel__dots" aria-label="Choose which images to display" ></div> <button class="carousel__toggle" type="button" aria-pressed="true" > Pause autoplay </button> </div> <p class="carousel__status visually-hidden" aria-live="off"></p> </section>
The main <section> contains the complete carousel. Its aria-label gives the component an accessible name.
<section>
aria-label
The important elements are:
.three-image-carousel
.carousel__viewport
.carousel__track
.carousel__slide
.carousel__button--prev
.carousel__button--next
.carousel__dots
.carousel__toggle
.carousel__status
The data-autoplay attribute controls whether the carousel starts automatically:
data-autoplay
data-autoplay="true"
Change it to false to disable automatic movement:
false
data-autoplay="false"
The data-interval value controls the delay between automatic transitions in milliseconds:
data-interval
data-interval="4500"
A value of 4500 means the slider changes every 4.5 seconds.
4500
Paste the following code into the CSS panel:
.three-image-carousel, .three-image-carousel * { box-sizing: border-box; } .three-image-carousel { --carousel-gap: 18px; --carousel-radius: 14px; --carousel-control-size: 44px; width: 100%; max-width: 1200px; margin: 40px auto; font-family: Arial, Helvetica, sans-serif; } .carousel__viewport { position: relative; width: 100%; overflow: hidden; border-radius: var(--carousel-radius); touch-action: pan-y; outline-offset: 5px; } .carousel__track { display: flex; gap: var(--carousel-gap); padding: 0; margin: 0; list-style: none; transition: transform 0.45s ease; will-change: transform; } .carousel__slide { flex: 0 0 100%; min-width: 0; } .carousel__slide figure { position: relative; overflow: hidden; height: 100%; margin: 0; border-radius: var(--carousel-radius); background: #f1f2f5; } .carousel__slide img { display: block; width: 100%; aspect-ratio: 3 / 2; height: auto; object-fit: cover; user-select: none; -webkit-user-drag: none; } .carousel__slide figcaption { position: absolute; right: 12px; bottom: 12px; left: 12px; padding: 10px 12px; border-radius: 8px; color: #ffffff; font-size: 15px; font-weight: 600; line-height: 1.4; background: rgba(0, 0, 0, 0.62); } .carousel__button { position: absolute; top: 50%; z-index: 2; display: grid; width: var(--carousel-control-size); height: var(--carousel-control-size); padding: 0; place-items: center; transform: translateY(-50%); border: 0; border-radius: 50%; color: #ffffff; background: rgba(0, 0, 0, 0.72); font-size: 22px; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; } .carousel__button:hover { background: rgba(0, 0, 0, 0.92); } .carousel__button:focus-visible, .carousel__toggle:focus-visible, .carousel__dot:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; } .carousel__button:disabled { opacity: 0.35; cursor: not-allowed; } .carousel__button--prev { left: 12px; } .carousel__button--next { right: 12px; } .carousel__footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 18px; margin-top: 18px; } .carousel__dots { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; } .carousel__dot { width: 11px; height: 11px; padding: 0; border: 0; border-radius: 50%; background: #b8bbc3; cursor: pointer; transition: transform 0.2s ease, background-color 0.2s ease; } .carousel__dot:hover { transform: scale(1.2); } .carousel__dot.is-active { background: #222222; transform: scale(1.25); } .carousel__toggle { padding: 9px 14px; border: 1px solid #222222; border-radius: 7px; color: #222222; background: #ffffff; font-size: 14px; font-weight: 600; cursor: pointer; } .carousel__toggle:hover { color: #ffffff; background: #222222; } .visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; } /* Two images on tablets */ @media (min-width: 768px) { .carousel__slide { flex-basis: calc((100% - var(--carousel-gap)) / 2); } } /* Three images on desktop */ @media (min-width: 1024px) { .carousel__slide { flex-basis: calc( (100% - var(--carousel-gap) - var(--carousel-gap)) / 3 ); } } @media (max-width: 767px) { .three-image-carousel { --carousel-gap: 12px; --carousel-control-size: 40px; } .carousel__button--prev { left: 8px; } .carousel__button--next { right: 8px; } .carousel__slide figcaption { font-size: 14px; } } @media (prefers-reduced-motion: reduce) { .carousel__track, .carousel__button, .carousel__dot { transition: none; } }
Each slide uses flex-basis to control how much of the visible carousel area it occupies.
flex-basis
On mobile, every slide occupies the full width:
.carousel__slide { flex: 0 0 100%; }
On tablets, the visible width is divided between two slides:
@media (min-width: 768px) { .carousel__slide { flex-basis: calc((100% - var(--carousel-gap)) / 2); } }
On desktop screens, the available width is divided between three slides:
@media (min-width: 1024px) { .carousel__slide { flex-basis: calc( (100% - var(--carousel-gap) - var(--carousel-gap)) / 3 ); } }
The calculation subtracts the spaces between the visible images before dividing the remaining width.
Paste this code into the JavaScript panel:
document.addEventListener('DOMContentLoaded', () => { document .querySelectorAll('.three-image-carousel') .forEach(initCarousel); }); function initCarousel(carousel) { const track = carousel.querySelector('.carousel__track'); const slides = Array.from( carousel.querySelectorAll('.carousel__slide') ); const previousButton = carousel.querySelector( '.carousel__button--prev' ); const nextButton = carousel.querySelector( '.carousel__button--next' ); const dotsContainer = carousel.querySelector('.carousel__dots'); const autoplayButton = carousel.querySelector('.carousel__toggle'); const status = carousel.querySelector('.carousel__status'); if ( !track || slides.length === 0 || !previousButton || !nextButton ) { return; } let currentIndex = 0; let autoplayTimer = null; let resizeTimer = null; let pointerStartX = null; let autoplayEnabled = carousel.dataset.autoplay === 'true'; const autoplayInterval = Math.max( Number.parseInt(carousel.dataset.interval, 10) || 4500, 2000 ); const reducedMotion = window.matchMedia( '(prefers-reduced-motion: reduce)' ); if (reducedMotion.matches) { autoplayEnabled = false; } slides.forEach((slide, index) => { slide.setAttribute('role', 'group'); slide.setAttribute('aria-roledescription', 'slide'); slide.setAttribute( 'aria-label', `${index + 1} of ${slides.length}` ); }); function visibleSlides() { if (window.innerWidth >= 1024) { return 3; } if (window.innerWidth >= 768) { return 2; } return 1; } function maximumIndex() { return Math.max(0, slides.length - visibleSlides()); } function slideStep() { const slideWidth = slides[0].getBoundingClientRect().width; const gap = Number.parseFloat(getComputedStyle(track).columnGap) || 0; return slideWidth + gap; } function buildDots() { if (!dotsContainer) { return; } dotsContainer.innerHTML = ''; const totalPositions = maximumIndex() + 1; for (let index = 0; index < totalPositions; index += 1) { const dot = document.createElement('button'); dot.type = 'button'; dot.className = 'carousel__dot'; dot.setAttribute( 'aria-label', `Show images starting with image ${index + 1}` ); dot.addEventListener('click', () => { currentIndex = index; updateCarousel(true); restartAutoplay(); }); dotsContainer.appendChild(dot); } } function updateCarousel(announce = false) { const maxIndex = maximumIndex(); currentIndex = Math.min( Math.max(currentIndex, 0), maxIndex ); track.style.transform = `translateX(-${currentIndex * slideStep()}px)`; const visibleCount = visibleSlides(); slides.forEach((slide, index) => { const isVisible = index >= currentIndex && index < currentIndex + visibleCount; slide.setAttribute( 'aria-hidden', String(!isVisible) ); }); const dots = Array.from( dotsContainer?.children || [] ); dots.forEach((dot, index) => { const isCurrent = index === currentIndex; dot.classList.toggle('is-active', isCurrent); dot.setAttribute( 'aria-current', isCurrent ? 'true' : 'false' ); }); const hasMovement = slides.length > visibleCount; previousButton.disabled = !hasMovement; nextButton.disabled = !hasMovement; if (status) { const first = currentIndex + 1; const last = Math.min( currentIndex + visibleCount, slides.length ); status.setAttribute( 'aria-live', announce ? 'polite' : 'off' ); status.textContent = `Showing images ${first} to ${last} of ${slides.length}`; } } function move(direction) { const maxIndex = maximumIndex(); if (maxIndex === 0) { return; } if (direction > 0) { currentIndex = currentIndex >= maxIndex ? 0 : currentIndex + 1; } else { currentIndex = currentIndex <= 0 ? maxIndex : currentIndex - 1; } updateCarousel(true); restartAutoplay(); } function stopAutoplay() { window.clearInterval(autoplayTimer); autoplayTimer = null; } function startAutoplay() { stopAutoplay(); if ( !autoplayEnabled || reducedMotion.matches || maximumIndex() === 0 ) { return; } autoplayTimer = window.setInterval(() => { const maxIndex = maximumIndex(); currentIndex = currentIndex >= maxIndex ? 0 : currentIndex + 1; updateCarousel(); }, autoplayInterval); } function restartAutoplay() { startAutoplay(); } function updateAutoplayButton() { if (!autoplayButton) { return; } autoplayButton.textContent = autoplayEnabled ? 'Pause autoplay' : 'Start autoplay'; autoplayButton.setAttribute( 'aria-pressed', String(autoplayEnabled) ); } previousButton.addEventListener('click', () => { move(-1); }); nextButton.addEventListener('click', () => { move(1); }); carousel.addEventListener('keydown', event => { if (event.key === 'ArrowLeft') { event.preventDefault(); move(-1); } if (event.key === 'ArrowRight') { event.preventDefault(); move(1); } }); carousel.addEventListener( 'mouseenter', stopAutoplay ); carousel.addEventListener( 'mouseleave', startAutoplay ); carousel.addEventListener( 'focusin', stopAutoplay ); carousel.addEventListener('focusout', () => { window.setTimeout(() => { if (!carousel.contains(document.activeElement)) { startAutoplay(); } }, 0); }); carousel.addEventListener('pointerdown', event => { if (event.target.closest('button')) { return; } pointerStartX = event.clientX; }); carousel.addEventListener('pointerup', event => { if (pointerStartX === null) { return; } const distance = event.clientX - pointerStartX; pointerStartX = null; if (Math.abs(distance) < 50) { return; } move(distance < 0 ? 1 : -1); }); carousel.addEventListener('pointercancel', () => { pointerStartX = null; }); autoplayButton?.addEventListener('click', () => { autoplayEnabled = !autoplayEnabled; updateAutoplayButton(); startAutoplay(); }); window.addEventListener('resize', () => { window.clearTimeout(resizeTimer); resizeTimer = window.setTimeout(() => { buildDots(); updateCarousel(); startAutoplay(); }, 150); }); reducedMotion.addEventListener?.( 'change', startAutoplay ); buildDots(); updateAutoplayButton(); updateCarousel(); startAutoplay(); }
The script finds every element with the .three-image-carousel class:
document .querySelectorAll('.three-image-carousel') .forEach(initCarousel);
Each carousel is initialized separately. This means the same code can manage multiple sliders on one WordPress page without requiring unique IDs.
The visibleSlides() function matches the CSS breakpoints:
visibleSlides()
function visibleSlides() { if (window.innerWidth >= 1024) { return 3; } if (window.innerWidth >= 768) { return 2; } return 1; }
Keep these values synchronized with your CSS if you change the responsive breakpoints.
The script measures the width of one slide and adds the gap between slides:
function slideStep() { const slideWidth = slides[0].getBoundingClientRect().width; const gap = Number.parseFloat(getComputedStyle(track).columnGap) || 0; return slideWidth + gap; }
The track is then moved horizontally:
track.style.transform = `translateX(-${currentIndex * slideStep()}px)`;
Using the measured pixel width prevents gaps, incorrect positioning, and blank spaces at the end of the carousel.
The maximum starting position depends on the total number of slides and the number currently visible:
function maximumIndex() { return Math.max(0, slides.length - visibleSlides()); }
For example, if the slider contains six images and displays three at a time, the last valid starting position is image four. Images four, five, and six will then appear together.
The dots are rebuilt when the browser size changes. This is necessary because a desktop layout and a mobile layout have different numbers of valid carousel positions.
Autoplay:
Follow these steps:
No JavaScript library needs to be added through the Pen settings because the carousel uses vanilla JavaScript.
CodePen normally provides separate HTML, CSS, and JavaScript editing panels. It also provides an Embed Builder that allows you to select visible tabs, adjust the embed height, choose a theme, and enable click-to-load behavior.
Embedding the Pen is useful when you want visitors to see or experiment with the code directly.
After saving the Pen:
CodePen also supports pasting a Pen URL into WordPress or using its provided HTML embed code. The full embed code offers more control over settings such as height, tabs, theme, and click-to-load behavior.
A CodePen embed is suitable for a tutorial or live demonstration. For a production website carousel, adding the code directly to WordPress usually gives you more control over performance, styling, image URLs, and maintenance.
There are several ways to install the slider on a WordPress website.
This is the most practical method for users who do not want to edit theme files.
WordPress provides the Custom HTML block for adding custom markup. Depending on the WordPress version, hosting configuration, and user permissions, disallowed tags such as scripts may be sanitized when the post is saved. For that reason, it is safer to keep the HTML in the block and load the CSS and JavaScript separately.
Paste the carousel CSS into one of the following locations:
Add the JavaScript through:
Set the script to load in the footer whenever that option is available.
This method is suitable when you control the website theme and want a maintainable production implementation.
Create these files in your child theme:
your-child-theme/ ├── assets/ │ ├── css/ │ │ └── three-image-slider.css │ └── js/ │ └── three-image-slider.js └── functions.php
Place the CSS in:
/assets/css/three-image-slider.css
Place the JavaScript in:
/assets/js/three-image-slider.js
Add the following PHP code to the child theme’s functions.php file:
functions.php
<?php function codecanel_three_image_slider_assets() { wp_enqueue_style( 'codecanel-three-image-slider', get_stylesheet_directory_uri() . '/assets/css/three-image-slider.css', array(), '1.0.0' ); wp_enqueue_script( 'codecanel-three-image-slider', get_stylesheet_directory_uri() . '/assets/js/three-image-slider.js', array(), '1.0.0', true ); } add_action( 'wp_enqueue_scripts', 'codecanel_three_image_slider_assets' );
WordPress recommends using the wp_enqueue_scripts hook for front-end styles and scripts rather than hardcoding asset tags into theme templates.
wp_enqueue_scripts
You can then place the slider HTML in a Custom HTML block, template file, reusable pattern, or custom block.
To avoid loading the carousel assets on every page, add a conditional check:
<?php function codecanel_three_image_slider_assets() { if ( ! is_page( 'slider-demo' ) ) { return; } wp_enqueue_style( 'codecanel-three-image-slider', get_stylesheet_directory_uri() . '/assets/css/three-image-slider.css', array(), '1.0.0' ); wp_enqueue_script( 'codecanel-three-image-slider', get_stylesheet_directory_uri() . '/assets/js/three-image-slider.js', array(), '1.0.0', true ); } add_action( 'wp_enqueue_scripts', 'codecanel_three_image_slider_assets' );
Replace slider-demo with the slug of the page containing your carousel.
slider-demo
The sample CodePen uses external placeholder images. Replace them before publishing the slider on a production site.
To find a WordPress image URL:
src
Example:
<img src="https://example.com/wp-content/uploads/portfolio-project.jpg" alt="Responsive website design displayed on a laptop" width="900" height="600" loading="lazy" decoding="async" >
For the first images visible when the page loads, remove loading="lazy". Images initially outside the visible area can use lazy loading.
loading="lazy"
Duplicate one of the <li> elements inside .carousel__track:
<li>
<li class="carousel__slide"> <figure> <img src="your-image-url.jpg" alt="Describe the image" width="900" height="600" loading="lazy" decoding="async" > <figcaption>Your Image Caption</figcaption> </figure> </li>
You do not need to change the JavaScript. It automatically detects the total number of slides and creates the correct navigation dots.
A carousel displaying three items at a time should normally contain at least four images. Six to nine images usually provide a more useful browsing experience without making the carousel excessively long.
Remove each <figcaption> element:
<figcaption>
<figcaption>Mountain Landscape</figcaption>
The slider will continue to function normally.
You can also keep the <figure> wrapper without the caption or replace it with a simpler structure.
<figure>
The example uses this image ratio:
.carousel__slide img { aspect-ratio: 3 / 2; }
For square images, use:
aspect-ratio: 1 / 1;
For wider images, use:
aspect-ratio: 16 / 9;
For portrait images, use:
aspect-ratio: 4 / 5;
Because the slider applies object-fit: cover, portions of an image may be cropped to fill the selected ratio.
object-fit: cover
Use contain instead when the complete image must remain visible:
contain
.carousel__slide img { object-fit: contain; }
This may create blank space around images with different proportions.
The current layout displays one, two, or three images depending on screen width.
Replace the desktop CSS with:
@media (min-width: 1024px) { .carousel__slide { flex-basis: calc( ( 100% - var(--carousel-gap) - var(--carousel-gap) - var(--carousel-gap) ) / 4 ); } }
Then update the JavaScript:
function visibleSlides() { if (window.innerWidth >= 1024) { return 4; } if (window.innerWidth >= 768) { return 2; } return 1; }
Remove the responsive slide-width rules and use:
.carousel__slide { flex: 0 0 calc( (100% - var(--carousel-gap) - var(--carousel-gap)) / 3 ); }
However, permanently displaying three images can make the content too narrow on mobile. A responsive one-image mobile layout is generally easier to view and interact with.
The movement speed is controlled by:
.carousel__track { transition: transform 0.45s ease; }
For a faster transition:
transition: transform 0.25s ease;
For a slower transition:
transition: transform 0.8s ease;
Avoid extremely slow transitions because they can make the interface feel unresponsive.
Change the carousel attribute from:
to:
The pause button will become a start button, allowing visitors to activate autoplay manually.
You can completely remove the autoplay button from the HTML if autoplay is never required:
<button class="carousel__toggle" type="button" aria-pressed="true" > Pause autoplay </button>
The JavaScript uses optional selection for this element, so removing it will not break the carousel.
Change the data-interval value:
data-interval="6000"
This changes the delay to six seconds.
The JavaScript enforces a minimum interval of two seconds to avoid extremely rapid automatic movement.
Copy the complete carousel HTML and paste another instance elsewhere on the page.
<section class="three-image-carousel" aria-label="Featured products" data-autoplay="true" data-interval="5000" > <!-- First slider content --> </section> <section class="three-image-carousel" aria-label="Recent portfolio projects" data-autoplay="false" data-interval="5000" > <!-- Second slider content --> </section>
The JavaScript initializes every .three-image-carousel independently:
Each carousel can therefore have:
Do not replace the reusable class with a unique ID unless your project specifically requires one.
A carousel should not depend only on automatic movement or pointer interaction.
The example includes:
W3C carousel guidance recommends placing a carousel inside a labeled region and representing its collection of items with semantic list markup where appropriate. It also emphasizes usable controls and the ability to stop movement.
Alternative text should describe the image’s relevant content or purpose.
Weak alt text:
alt="Image 1"
More useful alt text:
alt="Modern living room after an interior renovation"
Do not insert the same SEO keyword into every image description. Repetitive or unrelated alt text is not helpful to visitors.
When a slide includes a price, product name, offer, or important message, add that information as real HTML text. Do not rely on text embedded inside the image.
Automatic movement can make content difficult to follow. Give visitors a clear way to pause it and respect reduced-motion preferences.
An image carousel does not automatically improve search rankings. Its SEO value depends on the content, image relevance, loading performance, accessibility, and surrounding page information.
Instead of:
IMG_0048.jpg
Use:
responsive-wordpress-carousel-example.jpg
The filename should describe the actual image rather than repeat the same target keyword across every file.
Write alt text based on what each image communicates.
For example:
alt="Three responsive product cards displayed in a WordPress carousel"
Google recommends placing images near relevant text, using short descriptive filenames, and writing useful contextual alt text without keyword stuffing.
The tutorial includes:
width="900" height="600"
These dimensions allow the browser to reserve the correct space before the image finishes loading, reducing unexpected layout movement.
Do not upload a 4,000-pixel photograph when the image will be displayed at only a few hundred pixels wide.
Create suitable WordPress image sizes or use responsive image markup so the browser can select an appropriate source.
WebP and AVIF can often provide smaller file sizes than traditional formats while maintaining useful visual quality.
Keep a suitable fallback when required by your project.
WordPress commonly generates srcset and sizes attributes for images selected through its Media Library. These attributes help browsers choose a suitable image size.
srcset
sizes
A manual example looks like this:
<img src="portfolio-900.jpg" srcset=" portfolio-450.jpg 450w, portfolio-900.jpg 900w, portfolio-1400.jpg 1400w " sizes=" (min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw " alt="Web design project displayed in a responsive carousel" width="900" height="600" >
Google supports responsive images through srcset and <picture> while recommending a normal src fallback.
<picture>
The first images displayed in the carousel may appear within the initial viewport. Avoid delaying an important above-the-fold image with lazy loading.
for later or offscreen slides.
Providing width and height attributes also helps the browser reserve image space and reduce layout shifts.
This tutorial does not require jQuery or a third-party carousel library. That reduces the number of dependencies required for this particular slider.
A custom solution is not automatically faster than every plugin. Performance still depends on image sizes, theme code, caching, hosting, and how the files are loaded.
WordPress sliders can sometimes experience layout, responsiveness, JavaScript, image-sizing, or theme-conflict issues. The following solutions cover the most common problems and help you quickly identify why a slider is not displaying or working correctly.
Check that the desktop media query is present:
Also confirm that another theme style is not overriding .carousel__slide.
Use your browser’s developer tools to inspect the active flex-basis value.
Confirm that:
If there are exactly three slides and the desktop layout shows three images, there is no additional content to reveal.
WordPress may sanitize scripts depending on permissions and configuration. Keep the HTML inside the Custom HTML block and load the JavaScript through a properly enqueued file, code-snippet tool, site-specific plugin, or custom block.
Blank space commonly appears when a script allows the track to move beyond its final valid position.
This tutorial prevents that with:
Make sure this logic has not been removed or changed.
Use a consistent aspect ratio and object-fit:
object-fit
.carousel__slide img { aspect-ratio: 3 / 2; object-fit: cover; }
For the best result, upload images with matching dimensions or proportions.
A WordPress theme may apply global styles to every <button> element.
<button>
The tutorial uses specific classes such as:
.carousel__button .carousel__toggle .carousel__dot
Increase selector specificity when necessary:
.three-image-carousel .carousel__button { background: rgba(0, 0, 0, 0.72); }
Avoid styling every button globally.
Check that these rules are present:
.three-image-carousel, .three-image-carousel * { box-sizing: border-box; } .carousel__viewport { width: 100%; overflow: hidden; } .carousel__slide { min-width: 0; }
Also inspect the surrounding WordPress column, group, or page-builder container for fixed widths.
Confirm that the pointer-event code has been included and that another script is not intercepting pointer gestures.
The viewport should include:
touch-action: pan-y;
This allows normal vertical page scrolling while supporting horizontal swipe detection.
Make sure the hover and focus events are included:
carousel.addEventListener( 'mouseenter', stopAutoplay ); carousel.addEventListener( 'focusin', stopAutoplay );
You can also disable autoplay entirely by changing data-autoplay to false.
The dots are calculated from the maximum valid starting position, not simply from the total number of images.
Resize the browser and confirm that buildDots() is called inside the resize handler.
buildDots()
This usually indicates one of the following:
Check the browser console and network panel for errors before changing the slider logic.
Use custom code when you need a specific layout and are comfortable maintaining HTML, CSS, and JavaScript.
Use a plugin when editors need to create and update sliders through the WordPress dashboard without modifying code.
A carousel presents separate images or cards one after another. A comparison slider is designed to reveal differences between multiple versions of the same subject.
If your actual goal is to compare three stages of the same image, rather than display three independent cards, Code Canel’s WP Before After Image Slider provides a three-image comparison feature, three labels, captions, shortcode generation, and carousel-related options.
Creating a WP slider with three images using HTML, CSS, and JavaScript gives you direct control over the layout, transitions, navigation, responsive behavior, and accessibility.
The carousel in this tutorial displays three images on desktop, two on tablets, and one on mobile. It also includes autoplay controls, navigation dots, swipe gestures, keyboard support, reduced-motion handling, and support for multiple sliders on the same page.
Test the code in CodePen first, replace the sample images with your own WordPress Media Library URLs, and then add the HTML, CSS, and JavaScript to your website using a reliable installation method.
Most importantly, optimize the actual content inside the carousel. Use useful images, descriptive captions, accurate alternative text, properly sized files, and clear navigation. A technically functional slider is valuable only when it helps visitors discover and understand your content.
This page was last edited on 10 July 2026, at 6:48 pm
Your email address will not be published. Required fields are marked *
Comment *
Name *
Email *
Website
Save my name, email, and website in this browser for the next time I comment.
How many people work in your company?Less than 1010-5050-250250+
By proceeding, you agree to our Privacy Policy