Newsletter

Hear about new files and fixes

I send an email when I publish something new or release a fix for a file you might already use. There’s no fixed schedule.

Every email has a one-click unsubscribe link, and none of them contain tracking pixels.

Etch component

Before / After Image

Compare two photos with a divider you can drag, click or move from the keyboard. Horizontal or vertical, any frame ratio, optional zoom, pan and fullscreen. Both photos are in the HTML with their alt text, so visitors without JavaScript still see them side by side. Everything is set in the Etch props panel.

Download .jsonSee the demo

Paste it on the Etch canvas with Ctrl+V or ⌘V, then click Save.

Stay up to date. Etch won’t update a pasted component for you. Subscribe and get one email when a new version is out.

Free on unlimited personal, client and commercial sites. Modify it however you like.License: FSL-1.1-MIT
Version
1.0.0-beta.3Changelog
Options
39 settings+ 40 CSS variablesSee all
Tested on Etch
1.7.1
Tested on WordPress
7.1.3
Live demo

10 comparisons in real layouts

Drag, click or hover over the photos, or press Tab and use the arrow keys. Switch to Mobile to see the layouts at phone width.

Live
  • Instances: 10
  • Modes: drag, click, hover
  • Handle styles: 3
  • Ratios: 5
Open in a new tab
Features

What it does

One pasted component and no setup. The comparison engine is bundled, so there is no plugin, no CDN, no Cloudimage account and no extra request.

  • Real images in the HTML

    Etch renders both photos with their alt text on the server. Without JavaScript, or when a photo fails to load, the comparison falls back to the two images side by side.

  • Drag, click or hover

    Drag the divider, click to move it there, or let it follow the mouse. The keyboard always works: arrow keys, Shift for steps of 10%, Home and End.

  • Accessible by default

    A named slider that screen readers announce as a percentage, visible focus, 44 px targets and alt text for each photo. Reduced motion skips the entrance animation.

  • Any frame ratio

    4 / 3, 16 / 9, square, portrait or your own, such as 21 / 9. Cover crops both photos to fill the frame, Contain shows them whole, and a variable sets the crop focus.

  • Zoom, pan and fullscreen

    Zoom up to 10× with buttons, keys, Ctrl / ⌘ + scroll or a pinch, move around the zoomed photo, and open the comparison in fullscreen.

  • Every word translatable

    Edit the labels, the zoom hint and every accessible name in the props panel. The slider value follows the page language, such as “50 %” in German.

  • 40 styling variables

    Handle, divider, labels, buttons, crop focus and the focus ring, set on any parent class. ACSS 4 tokens with plain fallbacks, so it works without Automatic.css too.

  • Live in the builder

    The canvas runs the real comparison, so every prop edit shows at once, without saving. Drag the divider or zoom right in the canvas to try a setting.

Install

Add it in four steps

No plugin and no build step. The component brings its own CSS and script with the comparison engine bundled, so nothing loads from a CDN. It works with or without Automatic.css 4.

Shows without its styles? It was pasted but not saved, and pasting again won’t repair it. Delete ED Before / After Image from your components, paste it again and click Save.

  1. Copy the componentUse Copy component at the top of this page, or download the .json file.
  2. Paste it on the Etch canvasSelect where it should go, press Ctrl+V or ⌘V, then click Save. Etch adds the component to your list right away but stores its CSS only when you save.
  3. Choose two photosIn Content, pick Before image and After image from the Media Library and describe each one in Before alt and After alt. Photos taken from the same spot, at the same size, line up best.
  4. Save and open the pageThe canvas runs the real comparison as soon as both photos are chosen, so every setting you change shows at once. On the page, visitors drag, click or use the keyboard.
Options

39 settings in the Etch props panel

Grouped as in the Etch props panel, where each setting has help text too. Only the two photos are required; until you choose them, a new comparison shows an empty frame with its labels.

Numbers are text fields

Etch has no number field yet, so Initial position, Custom viewport ratio, Duration, Delay and Maximum zoom are text fields. Type plain digits, such as 40 or 1200. A value outside its range is clamped, and one the component can’t read uses the default.

Custom viewport ratio takes width / height, such as 21 / 9, or a decimal, such as 1.85. Anything else uses 4 / 3.

Content

Before imageRequiredcontent.beforeImage
The photo on the left, or on top in vertical mode. Pick it from the Media Library.Empty by default.
Before altcontent.beforeAlt
Describes the before photo for screen readers. Leave it empty only when the photo is decorative.Empty by default.
After imageRequiredcontent.afterImage
The photo on the right, or at the bottom in vertical mode. Use the same size and framing as the before photo.Empty by default.
After altcontent.afterAlt
Describes the after photo. Screen readers read it separately, so make it stand on its own.Empty by default.
Show labelscontent.showLabels
On shows the two labels on the photo.Default: On
Before labelcontent.beforeLabel
Text of the before label, such as Original.Default: Before
After labelcontent.afterLabel
Text of the after label, such as Restored.Default: After
Label positioncontent.labelPosition
Top or Bottom of the photo. In vertical mode, Top puts both labels on the left and Bottom on the right.Default: Top

Comparison

Orientationcomparison.orientation
Horizontal moves the divider left and right; Vertical moves it up and down.Default: Horizontal
Initial position (%)comparison.initialPosition
Where the divider starts, from 0 to 100.Default: 50
Interaction modecomparison.mode
Drag moves the handle and also jumps to a click or tap. Click moves the divider wherever you click. Hover follows a mouse or trackpad. The keyboard works in every mode.Default: Drag
Handle stylecomparison.handleStyle
Arrows, Circle or a slim Line. Each keeps a hit area of at least 44 px.Default: Arrows
Viewport ratiocomparison.aspectRatio
The frame both photos share: 4 / 3, 16 / 9, 1 / 1, 3 / 4 or Custom.Default: 4 / 3
Custom viewport ratiocomparison.customAspectRatio
Used when Viewport ratio is Custom: width / height, such as 21 / 9, or a decimal, such as 1.85.Default: 4 / 3
Image fitcomparison.objectFit
Cover crops both photos to fill the frame. Contain shows them whole, with bands in the surface color.Default: Cover (crop to fill)

Entrance animation

Entrance animationanimation.enabled
On sweeps the divider to its start position when the comparison comes into view. Visitors who prefer reduced motion never see it.Default: Off
Duration (ms)animation.duration
Length of the sweep, from 0 to 10000 milliseconds.Default: 800
Delay (ms)animation.delay
Wait before the sweep, from 0 to 10000 milliseconds.Default: 0
Animate onceanimation.once
On plays once per page view. Off plays again each time the comparison comes back into view, until the visitor moves the divider.Default: On

Zoom and pan

Enable zoomzoom.enabled
Zoom with the buttons, the plus, minus and 0 keys, Ctrl / ⌘ + scroll or a pinch. Alt + arrow keys and four buttons move the zoomed photo.Default: Off
Maximum zoomzoom.maximum
How far it zooms in, from 1 to 10.Default: 4
Show zoom controlszoom.controls
On shows the zoom in, zoom out and reset buttons. The buttons that move the photo appear while it’s zoomed in, even when this is Off.Default: On
Zoom controls positionzoom.controlsPosition
Top or bottom; left, center or right.Default: Bottom right
Show scroll hintzoom.scrollHint
On briefly shows the Scroll hint text when a visitor scrolls over the photo without holding Ctrl or ⌘.Default: On

Fullscreen

Enable fullscreen buttonfullscreen
Adds a button that opens the comparison in fullscreen, in browsers that support it.Default: Off

Performance

Image loadingperformance.loading
Lazy loads the photos as they come near the screen. Use Eager for a comparison at the top of the page.Default: Lazy
Image sizeperformance.imageSize
The WordPress size both photos load. Pick the smallest that stays sharp at the displayed width; a photo smaller than the size loads its original.Default: Full (original upload)

Accessible names and translations

Comparison nameaccessibility.comparisonLabel
Accessible name of the whole comparison. Give each comparison on a page its own.Default: Before and after image comparison
Slider name and instructionsaccessibility.sliderLabel
What screen readers announce for the divider. They say how to adjust a slider themselves, so instructions you add must not mention only keys.Default: Image comparison divider
Zoom inaccessibility.zoomInLabel
Name of the zoom-in button.Default: Zoom in
Zoom outaccessibility.zoomOutLabel
Name of the zoom-out button.Default: Zoom out
Reset zoomaccessibility.resetZoomLabel
Name of the reset button.Default: Reset zoom
Move image leftaccessibility.panLeftLabel
Name of the button that moves the zoomed photo left.Default: Move image left
Move image rightaccessibility.panRightLabel
Name of the button that moves the zoomed photo right.Default: Move image right
Move image upaccessibility.panUpLabel
Name of the button that moves the zoomed photo up.Default: Move image up
Move image downaccessibility.panDownLabel
Name of the button that moves the zoomed photo down.Default: Move image down
Enter fullscreenaccessibility.enterFullscreenLabel
Name of the fullscreen button.Default: Enter fullscreen
Exit fullscreenaccessibility.exitFullscreenLabel
Its name while the comparison is in fullscreen.Default: Exit fullscreen
Scroll hintaccessibility.scrollHintText
Visible text of the zoom hint.Default: Ctrl / ⌘ + scroll or pinch to zoom
Styling: 40 CSS variables

Set them on any parent element or class, such as a gallery section. They all inherit, so no extra selectors or !important are needed. The same list sits at the top of the component’s CSS in Etch, where uncommenting a line changes every comparison, but a parent class is safer: it survives updates.

Frame

--ed-before-after-radius
Corner radius of the frame; labels and buttons follow it.Default: var(--radius, 0.25rem)
--ed-before-after-surface
Frame color while the photos load, and the bands beside a photo with Image fit Contain.Default: var(--neutral-ultra-light, #eee)
--ed-before-after-shadow
Frame shadow, such as var(--box-shadow-1).Default: none
--ed-before-after-fullscreen-bg
Background behind the photos in fullscreen.Default: #000

Crop focus

--ed-before-after-image-position
Crop focus of both photos, as an object-position such as 50% 20% to keep the top in view.Default: 50% 50%
--ed-before-after-before-image-position
Crop focus of the before photo only.Default: var(--ed-before-after-image-position, 50% 50%)
--ed-before-after-after-image-position
Crop focus of the after photo only.Default: var(--ed-before-after-image-position, 50% 50%)

Divider and handle

--ed-before-after-divider-width
Width of the divider line.Default: 3px
--ed-before-after-divider-color
Divider color.Default: #fff
--ed-before-after-divider-shadow
Divider shadow.Default: none
--ed-before-after-handle-size
Hit area of the handle and the default grip size. Values below 44px use 44px.Default: 44px
--ed-before-after-handle-grip-size
Visible grip of the Arrows and Circle handles. It may be smaller than 44px; the hit area stays.Default: max(44px, var(--ed-before-after-handle-size, 44px))
--ed-before-after-handle-bg
Handle fill.Default: #fff
--ed-before-after-handle-hover-bg
Handle fill on hover and while dragging.Default: var(--ed-before-after-handle-bg, #fff)
--ed-before-after-handle-color
Color of the arrows in the handle.Default: #222
--ed-before-after-handle-border
Handle outline.Default: var(--border, 1px solid #555)
--ed-before-after-handle-radius
Shape of the Arrows and Circle handles.Default: 50%
--ed-before-after-handle-shadow
Handle shadow.Default: none
--ed-before-after-handle-icon-size
Size of the arrows in the handle.Default: 20px

Labels

--ed-before-after-label-bg
Label fill, also for the captions in the builder and without JavaScript.Default: #222
--ed-before-after-label-color
Label text color.Default: #fff
--ed-before-after-label-size
Label text size.Default: var(--text-s, 0.875rem)
--ed-before-after-label-weight
Label weight.Default: 600
--ed-before-after-label-font-family
Label typeface.Default: inherit
--ed-before-after-label-padding
Space inside a label.Default: 0.35em 0.7em
--ed-before-after-label-radius
Label corners.Default: var(--ed-before-after-radius, var(--radius, 0.25rem))
--ed-before-after-control-inset
Distance of the labels from the edge of the photo.Default: var(--space-xs, 0.75rem)

Buttons and zoom hint

--ed-before-after-button-size
Size of the zoom, move and fullscreen buttons. Values below 44px use 44px.Default: 44px
--ed-before-after-button-inset
Distance of those buttons from the edge of the photo.Default: var(--space-xs, 0.75rem)
--ed-before-after-controls-bg
Button fill.Default: #fff
--ed-before-after-controls-color
Button icon color.Default: #222
--ed-before-after-controls-hover-bg
Overlay on a button on hover.Default: rgb(0 0 0 / 0.08)
--ed-before-after-controls-radius
Button corners.Default: var(--ed-before-after-radius, var(--radius, 0.5rem))
--ed-before-after-controls-shadow
Button shadow.Default: none
--ed-before-after-hint-bg
Fill of the zoom hint.Default: rgb(0 0 0 / 0.75)
--ed-before-after-hint-color
Text color of the zoom hint.Default: #fff
--ed-before-after-hint-size
Text size of the zoom hint.Default: var(--text-xs, 0.8125rem)

Focus

--ed-before-after-focus-color
Keyboard focus outline.Default: var(--focus-color, #005fcc)
--ed-before-after-focus-width
Width of the focus outline.Default: 3px
--ed-before-after-focus-halo
Ring between the handle and the focus outline, so focus stays visible on light and dark photos.Default: #fff
License

Free to use. Not for resale.

Licensed under FSL-1.1-MIT, the Functional Source License with an MIT future license. Each version becomes MIT-licensed two years after its release.

You can
  • Use it on unlimited personal, client and commercial websites
  • Modify and adapt it however you like
  • Use it in paid design and development work for your clients
You can’t
  • Sell it, or a modified version, as a component: in a library, bundle, template kit, plugin or marketplace listing
  • Offer it in another commercial product or service that competes with it

Keep the copyright and license notices in the code, and link to this page instead of re-hosting the download, so everyone gets fixes and updates. Read the full license

Third-party code

This component bundles the open-source library below. It keeps its own license, and FSL-1.1-MIT does not apply to it. Its notice is included in the component’s script and CSS; keep it when you use or change the component.

  • @cloudimage/before-after1.0.4© 2024 Scaleflex
Updates

How to update

Etch has no update button for pasted components. Pasting a newer version over an installed one changes nothing: Etch keeps the component and the CSS it already has. To update, reinstall in five steps.

  1. Note what you setWrite down the settings of each comparison, including its photos and alt text, and copy any CSS you changed in the component’s own styles.
  2. Delete the componentIn Etch, delete ED Before / After Image from your components. Every comparison disappears from every page that used it.
  3. Delete its stylesIn the Style Manager, search for ed-before-after and delete the five styles listed below.
  4. Paste the new versionCopy or download the new JSON from this page, paste it on the canvas and click Save. Etch creates the component and its CSS fresh.
  5. Put your comparisons backInsert a comparison wherever one was, choose its photos, re-enter its settings and save. Reload any other open builder tab.

Styles to delete in step 3

  • .ed-before-after
  • .ed-before-after__fallback
  • .ed-before-after__figure
  • .ed-before-after__caption
  • .ed-before-after__mount

Trade-offs

  • Placements are not kept. Etch doesn’t link old comparisons to the new component, so you insert each one again and re-enter its settings.
  • Edits inside the component’s CSS are replaced. Keep overrides on a parent class with the --ed-before-after-* variables; those survive an update.
  • Skipping a step leaves a mix. Without step 2, Etch keeps the old component. Without step 3, the new version runs on the old CSS.

Coming from 1.0.0-beta.2? Follow the five steps above. Settings, classes and styling variables are unchanged, so you can re-enter your settings as they were. Subscribe and you’ll get one email when the next version is out.

Changelog

What changed

Versions follow semantic versioning. When an update changes how existing options behave, its entry says so.

1.0.0-beta.3Latest
  • FixedOn phones and tablets, a two-finger pinch that starts on a comparison with zoom off now zooms the page, like anywhere else, and leaves the divider where it is. Before, the comparison blocked page zoom and the first finger moved the divider. With zoom on, a pinch still zooms the photo.
  • FixedThe fullscreen button no longer reports a pressed state next to its changing name. TalkBack read it as “checked. Exit fullscreen. Switch”; now it’s “Enter fullscreen” or “Exit fullscreen”, read as a button.
  • ChangedThe default slider name is now “Image comparison divider”. The old default, “Image comparison. Use arrow keys to move the divider.”, told phone users to use keys they don’t have; screen readers already say how to adjust a slider. A name you entered yourself is kept.
  • ChangedThe script and the CSS of .ed-before-after changed, so update the component and its styles together (see How to update). Props, Select values, classes and the 40 styling variables are the same.
  • TestedTalkBack on Android, Safari on iPhone and iPad in the iOS Simulator, and Chrome, Firefox, Microsoft Edge and WebKit on the desktop. With TalkBack, swiping up or down doesn’t move the divider, because Chrome doesn’t pass that gesture to the slider: focus the slider, then double-tap and hold and drag. It stays a beta until it’s checked on a real phone and the Shift and Alt + arrow keys are tried in Safari.
1.0.0-beta.2
  • ReleasedFirst public version. It stays a beta until NVDA, VoiceOver on iOS, TalkBack, Microsoft Edge and touch devices are tested.
Feedback

Tell me what this component needs

A bug, a missing option, or anything that made the component awkward to use in a real build — it all belongs here. The versions above are the ones it was tested on; if yours differ, say so.