Before and after image slider in pure CSS
Tags: css, images, comparison
Published: 2023-11-29 15:29:56

---
title: Before and after image slider in pure CSS
published: true
description: A component which lets the user swipe between two different images build from pure CSS, plus an alternative approach and a semantic discussion
tags: CSS, Images, Comparison
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x30ukgwb48lcut0ymnip.jpg
# published_at: 2023-08-10 06:37 +0000
---

<small>Image by [Sams.Wild](https://www.pexels.com/photo/drone-shot-of-trees-11424099/)</small>

You've probably seen those before-and-after image sliders kicking about. You know, the old [paint the house](https://www.youtube.com/watch?v=Bg21M2zwG9Q&t=136s). [Victor/Victoria](https://www.youtube.com/watch?v=5WjoAe2aymM&t=14s). [The switcharoo](https://www.youtube.com/watch?v=rMz7JBRbmNo&t=133s). There's no shortage of Wordpress plugins which you can pay $30 a month to access (imagine renting JavaScript!). I had a need to use one the other day, so naturally, I wanted to build it JavaScript free[^1].

To break a long tradition of recipe blogging, I'll cut directly to the chorus. Here's the Codepen:

{% codepen https://codepen.io/ross-angus/pen/ZEmZzZx %}

The <q>trick</q>, such that it is, is to (ab)use [the CSS `resize` property](https://developer.mozilla.org/en-US/docs/Web/CSS/resize) on a paragraph, so that the user has a means to slide one image over the other.

## Advantages

* Sweet, sweet semantic HTML
* Zero JavaScript
* Still legible if the user hits <q>reading mode</q>[^2]

[^2]: You poor Chrome users won't know the joy the rest of us feel when a cookie control applies a blur filter to a web page and we make it all go away with a single click on our toolbar. Well, [there was a brief window when you could feel this joy](https://www.howtogeek.com/423643/how-to-use-google-chromes-hidden-reader-mode/), but it's been switched off now.

## Disadvantages

* The user interface isn't great (much of the CSS is used to generate the wee tooltip, which shows users where to grab)
* It doesn't look like all the other image switcharoos
* As far as I can work out, it's impossible to fix this, using just CSS and `resize`

[^1]: Because if JavaScript costs money to rent, the only way to make it free would be to use zero JavaScript

## How it works

There's really not a lot to it. Inside a `figure` element are two paragraphs and a `figcaption`. The `figcaption` is hidden away with `position` and the second paragraph establishes the height and width of the `figure`. The first paragraph sits on top, using `position: absolute` and can be resized using that cheeky `resize: horizontal` declaration.

The only minor trick is how to set the width of the first paragraph so it's exactly half the width of the second image. This is done by setting the `display` property of the `figure` to `table`. This lets the `figure` follow the normal document flow, but also shrink-wraps the content inside. In that way, it's similar to a `float`, but without all the impact on layout for following elements.

Finally, to make it responsive, a `max-height` of `100%` is added to both images and the second image also has a `max-width` of `100%`. This stops the horizontal scroll bar from appearing and scales both images down with the size of their container.

Most of the rest of the CSS does two things:

1. Removes the `figcaption` from the document flow, so that it doesn't interfer with the height of the `figure`
1. Generates the tooltip which shows the user where to grab, in order to slide the images over each other.

There's two small additions to the markup: the first paragraph has a `tabindex` of `0` and an `autofocus` attribute. This is so it immediately falls into focus, on page load, which displays the tooltip.

Because the resize handle doesn't appear to be part of the <abbr title="Document Object Model">DOM</abbr>, it's possible to resize the image with both the tooltip showing, or not. It depends where the focus is.

But none of this focus and tooltip code is required for the resize to work.

The second markup addition is to add a `style` attribute to the `figure` tag which sets a CSS variable of `--start`. This defaults to `50%` and allows you to set a different initial width of the first image, when the page first loads.

## Earlier attempt

When attempting to build this sort of thing, my usual starting point is <q>what native HTML control kind of does this already?</q> The one which first sprung to mind was an input of the type `range`.

Luckily, [Ana Tudor has done all the hard work of styling range elements and has put them on CodePen](https://codepen.io/collection/DgYaMj/). I forked one of them, removed all the <abbr title="Internet Explorer">IE</abbr> styles and put an image on the part of the slider before the slider <q>thumb</q>, and a different image after the slider thumb.

Because I use [God's Own Browser](https://www.mozilla.org/), this all came together quickly and worked well. It looked, and behaved, just like one of the JavaScript switcharoos. Then I tested it in Chrome.

After debugging for a while, it became clear that Tudor's range sliders use a tiny bit of JavaScript to make the parts of the slider track before and after the thumb different colours. Specifically, Tudor's code uses an image gradient to simulate that there are two different elements before and after the thumb. The gradient has a hard colour stop under the thumb and JavaScript is used to move the position of this stop, depending upon the value of the range.

I didn't replicate this, so here's the Firefox-only version of the image switcharoo:

{% codepen https://codepen.io/ross-angus/pen/MWzxyJE %}

This is less than ideal for reasons beyond the Internet's stubborn insistence on using an inferior web browser:

* The images are defined in CSS - they are changed on a case-by-case basis by using CSS variables, but this is a clumsy fix[^3]
* In order to display the range control correctly, each instance of it needs a CSS variable representing the URL of each image, a width and a height. The `figure` based solution doesn't require the width and height.
* The markup isn't very semantic - it's a range control, with no meaningful content inside
* It doesn't work if the user hits the reading mode
* Hacking form elements to perform CSS tricks is generally frowned upon in the accessibility community (perhaps an attribute of `role="presentation"` would help?)

[^3]: CSS variables are wonderful but should be used to pass around style information, not content. Technically, the path to each image would count as content.

## Appendix: peeking behind the curtain

You know how when a civilian watches you code and you bring up the inspector pane and their mind is blown? They get a glimpse into what they probably call The Matrix and see a baffling jumble of code and controls for things they couldn't even imagine.

What if I told you there was a further level we could go down? I'm not talking about view source. I'm talking about burrowing into the contents of the shadow <abbr>DOM</abbr> itself.

Not the shadow <abbr>DOM</abbr> that you might create using your own code. But the shadow <abbr>DOM</abbr> which the browser natively uses, to build up form elements.

Go back to [my fork of Tudor's range control](https://codepen.io/ross-angus/pen/MWzxyJE?editors=1100) for a moment. Look at all those weird pseudo-elements which appear in the CSS - stuff like `-webkit-slider-runnable-track`, `-moz-range-track`, `-moz-range-progress` and `-webkit-slider-thumb`. You can probably find documentation of these elements somewhere, but there's also an option to show them directly in the inspector.

### Exploring the shadow DOM in Chrome

In Chrome, it's an option which you can find by doing the following:

* Open Inspector
* Click on the cog, top right, to get to the settings
* In Preferences (on the tabs on the left - preferences should be open by default anyway), scroll down until you see the <q>Elements</q> heading
* Make sure the checkbox next to <q>Show user agent shadow DOM</q> is checked

Here's what you'll see, if you inspect the range slider example:

![The HTML source in the inspector panel in Chrome. The shadow DOM node inside the input element has been expanded to show pseudo-elements inside.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g39vm8w4ii7eugbkgavp.png)

### Exploring the shadow DOM in Firefox

In Firefox, it's a *tiny* bit more complicated:

* Type `about:config` into the address bar. You'll see a scary message, warning you are about to step off the safe path, and start into the thickets of *Extreme Browser Hacking*
* Type `devtools.inspector.showAllAnonymousContent` into the search box and you should see the configuration setting appear below
* You can toggle the setting on or off using that funny little icon which looks like an arrow pointing both ways at the same time

Here's what you'll see, if you inspect the range slider example:

![The HTML source in the inspector panel in Firefox. The shadow DOM node inside the input element has been expanded to show pseudo-elements inside.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwpv0ps7p37jemfgraz8.png)

Not quite as useful as Chrome, unfortunately, as it doesn't name the pseudo-elements.