An image slider is an effective way to display product photos, portfolio projects, featured content, travel images, or promotional banners without taking up too much space.

Although many sliders depend on JavaScript, you can build a simple and responsive image slider using only HTML and CSS. CSS radio buttons can control a manual slider, while CSS keyframe animations can create an automatic slideshow.

In this blog, you will learn how to create:

You will also get complete source code that you can copy, customize, and use in your own project.

What Is an Image Slider?

An image slider, also called an image carousel or slideshow, displays a collection of images within the same section of a webpage.

Instead of placing every image vertically on the page, the slider shows one image at a time. Visitors can select another slide using navigation dots, arrows, thumbnails, or automatic transitions.

Image sliders are commonly used for:

The slider in this tutorial is a standard image carousel. It is different from a before-and-after comparison slider, which displays two versions of the same image using a draggable comparison handle.

Subscribe to our Newsletter

Stay updated with our latest news and offers.
Thanks for signing up!

Can You Create an Image Slider Without JavaScript?

Yes. A basic image slider can be created using only HTML and CSS.

For a manual slider, hidden radio buttons store the currently selected slide. Each navigation label connects to one radio button. When a visitor selects a radio button, a CSS sibling selector moves the slide track to the correct position.

For an automatic slider, CSS keyframe animation moves the track at predetermined intervals.

A CSS-only slider is suitable when you need:

  • A lightweight image slideshow
  • A small number of fixed slides
  • Simple navigation dots
  • Basic fade or sliding transitions
  • A decorative automatic slideshow
  • A slider that does not depend on a JavaScript library

JavaScript is usually more appropriate when you need dynamic slides, drag-and-swipe gestures, advanced previous and next controls, autoplay controls, analytics, or more complete accessibility management.

How the CSS Image Slider Works

Before writing the code, it helps to understand the main components.

Slider container

The outer container controls the slider width and hides anything that extends outside its visible area.

overflow: hidden;

Without this property, the other slides may appear beside the active image.

Slide track

The slide track contains all the individual slides. Flexbox places them in one horizontal row.

display: flex;

Individual slides

Each slide takes up 100% of the slider container’s width.

flex: 0 0 100%;

Radio-button controls

Every slide has a corresponding radio input. Selecting an input changes the position of the slide track.

CSS transform

The translateX() function moves the track horizontally.

transform: translateX(-100%);

A value of -100% displays the second image. A value of -200% displays the third image.

Project Folder Structure

Create a folder for the project and organize the files like this:

css-image-slider/
│
├── index.html
├── styles.css
└── images/
    ├── mountain-landscape.webp
    ├── city-night.webp
    └── beach-sunset.webp

You can use JPG, PNG, WebP, or AVIF images. WebP and AVIF usually provide smaller file sizes than older formats when properly optimized.

Replace the sample image names with your actual filenames.

Method 1: Manual Image Slider Using HTML and CSS

This first method creates a responsive CSS image slider with navigation dots. Visitors can manually choose which image to display.

Step 1: Create the HTML Structure

Create an index.html file and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1.0"
    >

    <title>Responsive CSS Image Slider</title>

    <link rel="stylesheet" href="styles.css">
</head>

<body>

    <main class="page-content">

        <section
            class="image-slider"
            aria-labelledby="slider-heading"
        >
            <h1 id="slider-heading" class="screen-reader-text">
                Featured landscape images
            </h1>

            <!-- Slider controls -->
            <input
                type="radio"
                name="image-slider"
                id="slide-control-1"
                aria-label="Show mountain landscape, slide 1 of 3"
                checked
            >

            <input
                type="radio"
                name="image-slider"
                id="slide-control-2"
                aria-label="Show city at night, slide 2 of 3"
            >

            <input
                type="radio"
                name="image-slider"
                id="slide-control-3"
                aria-label="Show beach sunset, slide 3 of 3"
            >

            <!-- Slide track -->
            <div class="slides">

                <figure class="slide">
                    <img
                        src="images/mountain-landscape.webp"
                        width="1200"
                        height="675"
                        alt="Snow-covered mountains beneath a clear blue sky"
                        loading="eager"
                    >

                    <figcaption class="slide-caption">
                        Mountain Landscape
                    </figcaption>
                </figure>

                <figure class="slide">
                    <img
                        src="images/city-night.webp"
                        width="1200"
                        height="675"
                        alt="Illuminated city buildings reflected in a river at night"
                        loading="lazy"
                    >

                    <figcaption class="slide-caption">
                        City at Night
                    </figcaption>
                </figure>

                <figure class="slide">
                    <img
                        src="images/beach-sunset.webp"
                        width="1200"
                        height="675"
                        alt="Orange sunset above a calm beach and ocean"
                        loading="lazy"
                    >

                    <figcaption class="slide-caption">
                        Beach Sunset
                    </figcaption>
                </figure>

            </div>

            <!-- Navigation dots -->
            <div class="slider-navigation" aria-label="Choose an image">

                <label for="slide-control-1">
                    <span class="screen-reader-text">
                        Show slide 1
                    </span>
                </label>

                <label for="slide-control-2">
                    <span class="screen-reader-text">
                        Show slide 2
                    </span>
                </label>

                <label for="slide-control-3">
                    <span class="screen-reader-text">
                        Show slide 3
                    </span>
                </label>

            </div>
        </section>

    </main>

</body>
</html>

Understanding the HTML

The outer section

The .image-slider section contains the complete slider, including its controls, images, captions, and navigation dots.

<section class="image-slider">

The radio buttons

The three radio buttons belong to the same group because they share the same name value:

name="image-slider"

Only one radio button from the group can be selected at a time.

The first input includes the checked attribute, so the first image appears when the page loads.

checked

The slide track

The .slides element holds every slide:

<div class="slides">

CSS will display these slides horizontally.

The navigation labels

Each navigation dot is a <label> connected to a radio input through its for attribute.

<label for="slide-control-2">

Selecting this label activates the input with the matching ID:

id="slide-control-2"

The for and id values must match exactly. Otherwise, the slider navigation will not work.

Step 2: Add the Complete CSS

Create a styles.css file and add the following code:

/* ------------------------------
   Basic page styles
------------------------------ */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    color-scheme: light;
}

body {
    margin: 0;
    min-height: 100vh;
    font-family: Arial, Helvetica, sans-serif;
    background: #f4f6fb;
    color: #172033;
}

img {
    max-width: 100%;
}

.page-content {
    width: min(100% - 32px, 1100px);
    margin-inline: auto;
    padding-block: 64px;
}

/* ------------------------------
   Screen-reader-only content
------------------------------ */

.screen-reader-text {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ------------------------------
   Slider container
------------------------------ */

.image-slider {
    position: relative;
    width: min(100%, 960px);
    margin-inline: auto;
    overflow: hidden;
    border-radius: 18px;
    background: #111827;
    box-shadow: 0 20px 50px rgb(15 23 42 / 18%);
    isolation: isolate;
}

/* Keep the radio buttons available to keyboard users
   while visually hiding them. */

.image-slider > input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    opacity: 0;
}

/* ------------------------------
   Slide track
------------------------------ */

.slides {
    display: flex;
    transition: transform 600ms ease;
    will-change: transform;
}

/* ------------------------------
   Individual slides
------------------------------ */

.slide {
    position: relative;
    flex: 0 0 100%;
    aspect-ratio: 16 / 9;
    margin: 0;
    overflow: hidden;
}

.slide img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
}

/* ------------------------------
   Image captions
------------------------------ */

.slide-caption {
    position: absolute;
    left: 24px;
    right: 24px;
    bottom: 24px;
    width: fit-content;
    max-width: calc(100% - 48px);
    padding: 10px 14px;
    border-radius: 8px;
    background: rgb(0 0 0 / 68%);
    color: #ffffff;
    font-size: clamp(0.875rem, 2vw, 1.125rem);
    line-height: 1.4;
}

/* ------------------------------
   Move the slide track
------------------------------ */

#slide-control-1:checked ~ .slides {
    transform: translateX(0);
}

#slide-control-2:checked ~ .slides {
    transform: translateX(-100%);
}

#slide-control-3:checked ~ .slides {
    transform: translateX(-200%);
}

/* ------------------------------
   Navigation dots
------------------------------ */

.slider-navigation {
    position: absolute;
    z-index: 2;
    left: 50%;
    bottom: 18px;
    display: flex;
    gap: 10px;
    transform: translateX(-50%);
}

.slider-navigation label {
    width: 14px;
    height: 14px;
    display: block;
    border: 2px solid #ffffff;
    border-radius: 50%;
    background: rgb(15 23 42 / 50%);
    cursor: pointer;
    transition:
        background-color 200ms ease,
        transform 200ms ease;
}

.slider-navigation label:hover {
    transform: scale(1.2);
}

/* Show the currently selected dot */

#slide-control-1:checked
~ .slider-navigation
label[for="slide-control-1"],

#slide-control-2:checked
~ .slider-navigation
label[for="slide-control-2"],

#slide-control-3:checked
~ .slider-navigation
label[for="slide-control-3"] {
    background: #ffffff;
}

/* Show keyboard focus around the matching dot */

#slide-control-1:focus-visible
~ .slider-navigation
label[for="slide-control-1"],

#slide-control-2:focus-visible
~ .slider-navigation
label[for="slide-control-2"],

#slide-control-3:focus-visible
~ .slider-navigation
label[for="slide-control-3"] {
    outline: 3px solid #38bdf8;
    outline-offset: 4px;
}

/* ------------------------------
   Responsive layout
------------------------------ */

@media (max-width: 600px) {
    .page-content {
        width: min(100% - 20px, 1100px);
        padding-block: 32px;
    }

    .image-slider {
        border-radius: 12px;
    }

    .slide {
        aspect-ratio: 4 / 3;
    }

    .slide-caption {
        left: 14px;
        right: 14px;
        bottom: 48px;
        max-width: calc(100% - 28px);
        padding: 8px 10px;
    }

    .slider-navigation {
        bottom: 16px;
    }

    .slider-navigation label {
        width: 16px;
        height: 16px;
    }
}

/* ------------------------------
   Reduced-motion preference
------------------------------ */

@media (prefers-reduced-motion: reduce) {
    .slides,
    .slider-navigation label {
        transition: none;
    }
}

Step 3: Open the Slider in Your Browser

Save both files and open index.html in a modern web browser.

The first image should appear automatically. Selecting the second or third navigation dot should move the slider horizontally to the corresponding image.

No JavaScript is required.

Why the Slider Code Works

The radio inputs appear before the .slides element in the HTML.

That placement is important because the CSS uses the general sibling selector:

#slide-control-2:checked ~ .slides

This selector means:

Find the .slides element that appears after the checked input with the ID slide-control-2.

The following rule moves the track one complete slider width to the left:

transform: translateX(-100%);

The third slide moves the track two slider widths:

transform: translateX(-200%);

Placing the inputs inside .slides would prevent these selectors from targeting the track correctly.

How to Customize the CSS Image Slider

The basic slider can be customized without changing its core functionality.

Change the slider width

Update the maximum width:

.image-slider {
    width: min(100%, 1200px);
}

The slider will grow to a maximum width of 1,200 pixels while remaining responsive on smaller screens.

Change the slider height

The example uses an aspect ratio instead of a fixed height:

.slide {
    aspect-ratio: 16 / 9;
}

For a taller slider, use:

aspect-ratio: 4 / 3;

For a square slider, use:

aspect-ratio: 1 / 1;

The aspect-ratio property establishes a preferred relationship between an element’s width and height. Combined with object-fit: cover, it allows images of different proportions to fill a consistent slide area.

Change the transition speed

The following rule controls how quickly one image moves to the next:

.slides {
    transition: transform 600ms ease;
}

For a faster transition:

transition: transform 300ms ease;

For a slower transition:

transition: transform 900ms ease;

Remove image captions

Delete the <figcaption> elements from the HTML:

<figcaption class="slide-caption">
    Mountain Landscape
</figcaption>

You can also keep the captions in the HTML and visually hide them when needed:

.slide-caption {
    display: none;
}

Change the navigation-dot color

Update the selected-dot background:

background: #ffffff;

For example:

background: #3d5afe;

Add rounded images

The slider container already includes:

border-radius: 18px;
overflow: hidden;

The overflow rule ensures that the images stay inside the rounded corners.

How to Add More Images

To add a fourth slide, complete four updates.

1. Add another radio button

<input
    type="radio"
    name="image-slider"
    id="slide-control-4"
    aria-label="Show forest waterfall, slide 4 of 4"
>

2. Add another slide

<figure class="slide">
    <img
        src="images/forest-waterfall.webp"
        width="1200"
        height="675"
        alt="Waterfall flowing through a green forest"
        loading="lazy"
    >

    <figcaption class="slide-caption">
        Forest Waterfall
    </figcaption>
</figure>

3. Add another navigation label

<label for="slide-control-4">
    <span class="screen-reader-text">
        Show slide 4
    </span>
</label>

4. Add the transform rule

#slide-control-4:checked ~ .slides {
    transform: translateX(-300%);
}

Also add the selected-dot and focus styles for the fourth control.

Each additional slide moves the track another 100% to the left:

  • First slide: 0
  • Second slide: -100%
  • Third slide: -200%
  • Fourth slide: -300%
  • Fifth slide: -400%

Method 2: Automatic Image Slider Using HTML and CSS

You can also create an automatic image slider using CSS keyframe animation.

This version changes images without requiring visitors to select navigation controls.

CSS-only autoplay works best for decorative image sequences. It is less suitable when the slides contain important text, links, forms, or information that users must be able to pause and control.

Automatic Slider HTML

Use the following HTML:

<section
    class="automatic-slider"
    aria-label="Automatic landscape slideshow"
    tabindex="0"
>
    <div class="automatic-slides">

        <figure class="automatic-slide">
            <img
                src="images/mountain-landscape.webp"
                width="1200"
                height="675"
                alt="Snow-covered mountains beneath a clear blue sky"
            >

            <figcaption>Mountain Landscape</figcaption>
        </figure>

        <figure class="automatic-slide">
            <img
                src="images/city-night.webp"
                width="1200"
                height="675"
                alt="Illuminated city buildings reflected in a river at night"
            >

            <figcaption>City at Night</figcaption>
        </figure>

        <figure class="automatic-slide">
            <img
                src="images/beach-sunset.webp"
                width="1200"
                height="675"
                alt="Orange sunset above a calm beach and ocean"
            >

            <figcaption>Beach Sunset</figcaption>
        </figure>

        <!-- Duplicate of the first slide for a smooth loop -->
        <figure class="automatic-slide" aria-hidden="true">
            <img
                src="images/mountain-landscape.webp"
                width="1200"
                height="675"
                alt=""
            >
        </figure>

    </div>
</section>

The first image is repeated at the end. This allows the animation to move from the third image to a duplicate of the first before resetting.

Automatic Slider CSS

Add the following CSS:

.automatic-slider {
    position: relative;
    width: min(100%, 960px);
    margin-inline: auto;
    overflow: hidden;
    border-radius: 18px;
    background: #111827;
    box-shadow: 0 20px 50px rgb(15 23 42 / 18%);
}

.automatic-slider:focus-visible {
    outline: 4px solid #38bdf8;
    outline-offset: 5px;
}

.automatic-slides {
    display: flex;
    animation: automatic-slide 15s infinite ease-in-out;
}

.automatic-slide {
    position: relative;
    flex: 0 0 100%;
    aspect-ratio: 16 / 9;
    margin: 0;
    overflow: hidden;
}

.automatic-slide img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
}

.automatic-slide figcaption {
    position: absolute;
    left: 24px;
    bottom: 24px;
    padding: 10px 14px;
    border-radius: 8px;
    background: rgb(0 0 0 / 68%);
    color: #ffffff;
    font-size: 1rem;
}

/* Pause while the slider is hovered or focused */

.automatic-slider:hover .automatic-slides,
.automatic-slider:focus-within .automatic-slides {
    animation-play-state: paused;
}

/* Automatic movement */

@keyframes automatic-slide {
    0%,
    26% {
        transform: translateX(0);
    }

    33%,
    59% {
        transform: translateX(-100%);
    }

    66%,
    92% {
        transform: translateX(-200%);
    }

    100% {
        transform: translateX(-300%);
    }
}

/* Disable automatic movement when reduced motion is requested */

@media (prefers-reduced-motion: reduce) {
    .automatic-slides {
        animation: none;
        transform: none;
    }

    .automatic-slide {
        display: none;
    }

    .automatic-slide:first-child {
        display: block;
    }
}

Understanding the Automatic Animation

The animation lasts 15 seconds:

animation: automatic-slide 15s infinite ease-in-out;

The infinite value restarts it continuously.

The keyframes hold each image in place before moving to the next one:

0%,
26% {
    transform: translateX(0);
}

The second image appears after the track moves left by 100%:

transform: translateX(-100%);

The third image appears at -200%.

The duplicate of the first image appears at -300%, creating a smoother visual loop when the animation resets.

How to Change the Autoplay Speed

Change the animation duration:

animation: automatic-slide 15s infinite ease-in-out;

For faster autoplay:

animation: automatic-slide 9s infinite ease-in-out;

For slower autoplay:

animation: automatic-slide 24s infinite ease-in-out;

Do not make slides move so quickly that visitors cannot understand their contents.

Manual Slider vs. Automatic Slider

FeatureManual CSS SliderAutomatic CSS Slider
Requires JavaScriptNoNo
Navigation dotsYesNo in this example
User controls timingYesLimited
CSS keyframes requiredNoYes
Good for essential contentBetterNot recommended
Easy to pause and restartNot applicableLimited with CSS only
Works with reduced-motion CSSYesYes
Best useGalleries and portfoliosDecorative image sequences

For most content-focused websites, the manual version is the safer choice because visitors control when the slide changes.

How to Make the Image Slider Responsive

The slider already uses several responsive techniques.

Use a fluid width

width: min(100%, 960px);

This allows the slider to fill its available container while preventing it from becoming wider than 960 pixels.

Use a consistent aspect ratio

aspect-ratio: 16 / 9;

This keeps the slider’s proportions consistent across different screen sizes.

Use object-fit

object-fit: cover;

This makes every image fill the available slide area. Parts of a particularly tall or wide image may be cropped.

Use contain when showing the entire image is more important than filling the complete slider area:

object-fit: contain;

This may create empty space around some images.

Avoid fixed pixel widths

Avoid code such as:

width: 960px;

A fixed width can make the slider overflow on smaller screens.

Use percentages, max-width, or the min() function instead.

Increase control sizes on mobile

Touch controls should be easy to select. The example increases the navigation-dot size on smaller screens:

@media (max-width: 600px) {
    .slider-navigation label {
        width: 16px;
        height: 16px;
    }
}

Do not stack the slides vertically

Changing the slide track to this on mobile will stop the carousel behavior:

.slides {
    flex-direction: column;
}

The slider depends on the images remaining in a horizontal row. Keep the default Flexbox direction and adjust the container dimensions instead.

How to Optimize Slider Images

Large, unoptimized slider images can affect loading speed, especially when several images load on the same page.

Resize images before uploading

Do not upload a 4,000-pixel image when the slider displays it at a maximum width of 960 pixels.

Prepare images close to the largest size at which they will be displayed.

For high-density screens, you may provide a larger version through srcset.

Use modern image formats

WebP and AVIF can often reduce file size while maintaining good visual quality.

Keep a compatible fallback when your audience or publishing system requires one.

Add descriptive filenames

Avoid filenames such as:

image1.jpg
photo-final-new.jpg
slider-img-3.png

Use descriptive filenames:

snow-covered-mountain-slider.webp
city-skyline-night-carousel.webp
orange-beach-sunset-slide.webp

Write useful alt text

Avoid generic alt text:

alt="Image 1"

Describe the image’s relevant content:

alt="Snow-covered mountains beneath a clear blue sky"

Google recommends descriptive alt text because it provides image context and supports people who cannot see the image. It also recommends responsive image techniques and standard image elements for image discovery.

Do not fill alt text with unrelated keywords. Describe the actual image and its purpose on the page.

Add image dimensions

Include width and height attributes:

<img
    src="images/mountain-landscape.webp"
    width="1200"
    height="675"
    alt="Snow-covered mountains beneath a clear blue sky"
>

This gives the browser information about the image proportions before the file finishes loading.

Use responsive images

For more control over image loading, use srcset and sizes:

<img
    src="images/mountain-landscape-1200.webp"

    srcset="
        images/mountain-landscape-480.webp 480w,
        images/mountain-landscape-800.webp 800w,
        images/mountain-landscape-1200.webp 1200w
    "

    sizes="
        (max-width: 600px) calc(100vw - 20px),
        960px
    "

    width="1200"
    height="675"

    alt="Snow-covered mountains beneath a clear blue sky"
>

The browser can select an appropriate image file based on the screen and layout.

Accessibility Considerations for CSS-Only Sliders

A CSS-only slider can support basic keyboard interaction, but it has limitations.

In the manual example, the radio buttons remain available in the keyboard navigation order. A keyboard user can focus the radio group and use arrow keys to change the selection.

Avoid hiding the inputs using:

display: none;

That removes them from the keyboard interaction flow in common browser behavior.

The example visually hides the controls while keeping them present:

opacity: 0;
width: 1px;
height: 1px;

Provide meaningful alternative text

Every informative image should include accurate alt text. Decorative images can use an empty alt attribute:

alt=""

Make focus visible

The example adds a focus outline to the navigation dot connected to the currently focused input.

Never remove focus outlines unless you replace them with another clear focus indicator.

Respect reduced-motion preferences

Use:

@media (prefers-reduced-motion: reduce) {
    .slides {
        transition: none;
    }
}

The prefers-reduced-motion media feature detects when a person has requested fewer nonessential animations through their device settings.

Be careful with autoplay

Automatically rotating carousels can be difficult for visitors who need more time to read or interact with content.

W3C carousel guidance recommends giving users control over automatic movement, stopping rotation when keyboard focus enters the carousel, pausing it on hover, and providing a way to stop and restart it. A CSS-only autoplay slider cannot provide all of this behavior as reliably as a scripted, accessibility-focused component.

Use the automatic CSS example for decorative images rather than essential information.

Choose a JavaScript-based accessible carousel when you need:

  • A visible play-and-pause button
  • Previous and next buttons
  • Announcements for screen readers
  • Dynamic slide visibility
  • Reliable focus management
  • Swipe gestures
  • Complex content inside each slide

Common CSS Image Slider Problems and Solutions

The slider shows every image at once

Possible cause: The container does not hide overflowing slides.

Add:

.image-slider {
    overflow: hidden;
}

Also confirm that the slides are arranged horizontally:

.slides {
    display: flex;
}

Clicking the navigation dots does nothing

Confirm that each label’s for value matches its input ID.

Correct:

<input id="slide-control-2" type="radio" name="image-slider">

<label for="slide-control-2"></label>

Incorrect:

<input id="slide-2" type="radio" name="image-slider">

<label for="slide-control-2"></label>

The values do not match in the incorrect example.

The CSS checked selector is not working

The input must appear before the element targeted by the sibling selector.

Correct structure:

<input type="radio" id="slide-control-1">

<div class="slides">
    <!-- Slides -->
</div>

This selector can now work:

#slide-control-1:checked ~ .slides {
    transform: translateX(0);
}

If the input is placed inside .slides, it cannot select the parent track using a sibling selector.

Images have different heights

Give every slide the same aspect ratio:

.slide {
    aspect-ratio: 16 / 9;
}

Then make the images fill that area:

.slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

The image is cropped

Cropping is caused by:

object-fit: cover;

Change it to:

object-fit: contain;

This displays the entire image but may leave empty space around it.

The slider overflows on mobile

Check for fixed widths:

width: 960px;

Replace them with responsive values:

width: min(100%, 960px);

Also ensure the page container has reasonable horizontal spacing.

The third slide does not appear

Check its transform value.

For three full-width slides:

#slide-control-1:checked ~ .slides {
    transform: translateX(0);
}

#slide-control-2:checked ~ .slides {
    transform: translateX(-100%);
}

#slide-control-3:checked ~ .slides {
    transform: translateX(-200%);
}

The automatic slider jumps when restarting

A looping animation resets from its final position to its initial position.

To make that reset less noticeable, duplicate the first image at the end of the track. Move to the duplicate during the final keyframe, then let the animation reset to the original first image.

Because the two images are visually identical, the reset is not noticeable.

The automatic slider does not pause

Add:

.automatic-slider:hover .automatic-slides,
.automatic-slider:focus-within .automatic-slides {
    animation-play-state: paused;
}

Remember that hover is not available in the same way on every touchscreen device.

The navigation dots appear behind the images

Give the navigation container a higher stacking level:

.slider-navigation {
    z-index: 2;
}

The slider container should also use:

position: relative;

The captions are difficult to read

Use a sufficiently opaque background behind the text:

.slide-caption {
    background: rgb(0 0 0 / 68%);
    color: #ffffff;
}

Avoid placing unprotected text directly over detailed or high-contrast images.

CSS-Only Slider vs. JavaScript Slider

Both approaches have valid uses.

CapabilityCSS-Only SliderJavaScript Slider
Simple fixed image setExcellentExcellent
No script dependencyYesNo
Manual navigation dotsYesYes
Automatic animationBasicAdvanced
Previous and next arrowsPossible but verboseEasy
Swipe and drag supportVery limitedYes
Dynamically generated slidesDifficultEasy
Autoplay pause buttonLimitedYes
Screen-reader announcementsLimitedManageable
Advanced focus managementLimitedManageable
Analytics eventsNoYes
Lightweight setupExcellentDepends on implementation

Use CSS only when your requirements are simple and fixed. Use JavaScript when the slider is an important interactive component rather than a basic visual effect.

Best Practices for an HTML and CSS Image Slider

Follow these guidelines when adding a slider to a real website:

  1. Keep the number of slides manageable.
  2. Use consistent image dimensions.
  3. Compress images before uploading them.
  4. Add meaningful alt text.
  5. Make navigation controls visible.
  6. Keep controls large enough for touch interaction.
  7. Avoid placing essential information only inside an autoplay slider.
  8. Respect reduced-motion preferences.
  9. Test the slider with a keyboard.
  10. Test it on different screen sizes.
  11. Avoid excessively fast animations.
  12. Use JavaScript for complex accessibility and interaction requirements.

Conclusion

Creating a responsive image slider in HTML and CSS without JavaScript is possible with radio buttons, labels, Flexbox, CSS transforms, and keyframe animations.

The manual version is ideal for portfolios, product images, travel photos, galleries, and other situations where visitors should control the active image. The automatic version can work for decorative slideshows, but it should not be used for essential information unless users have complete control over the movement.

Start with the manual slider source code, replace the sample images, update the captions and alt text, and adjust the colors to match your website.

For more advanced requirements such as swipe navigation, dynamic slides, previous and next buttons, autoplay controls, and screen-reader announcements, use an accessibility-focused JavaScript solution.

For WordPress projects that need an interactive before-and-after comparison rather than a standard carousel, Code Canel’s WP Before After Image Slider provides a dedicated no-code option.

Frequently Asked Questions

Can I create an image slider using only HTML and CSS?

Yes. Radio inputs, labels, Flexbox, sibling selectors, and CSS transforms can create a manual image slider without JavaScript. CSS keyframes can also create automatic slide movement.

How do I make an automatic image slider without JavaScript?

Place the images inside a horizontal Flexbox track and apply a repeating @keyframes animation to that track. At different animation percentages, use translateX() to display the next image.

How do I make a CSS image slider responsive?

Use a fluid width such as width: 100%, set a reasonable max-width, give the slides an aspect-ratio, and apply object-fit: cover to the images. Avoid fixed widths that are larger than mobile screens.

How do I add more images to the slider?

Add another radio input, slide element, navigation label, and checked-state transform rule. Every new slide moves the track another 100% to the left.

Can I add previous and next arrows without JavaScript?

Yes, but the HTML becomes more complicated. You must display different labels for the previous and next destination based on the currently selected radio input. Navigation dots are simpler and easier to maintain in a basic CSS-only tutorial.

Why is my CSS image slider not working?

The most common causes are mismatched label and input values, incorrect HTML order, missing overflow: hidden, incorrect transform percentages, or slides that do not use a full-width flex basis.

Is a CSS-only image slider accessible?

A simple manual slider can provide basic keyboard support when its radio inputs remain focusable. However, CSS alone cannot reliably manage every accessibility requirement of an advanced carousel, especially automatic rotation, changing announcements, dynamic slide visibility, and focus control.

This page was last edited on 23 July 2026, at 6:15 pm