Transitions✨ Premium

Split Compare

The same component in both its themes, one over the other, and a 'VS' divider you drag to uncover one or the other: by mouse, by finger or with the arrow keys, thanks to an invisible native slider laid over the whole scene. 0.45 KB of JavaScript; without the script, the comparison stays fixed at 50%.

CSSCompareSplit

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Transitions has 52 effects, including 9 free. Explore the category →

3 usage examples

Picking a theme on first launch — an online bank's app — Split Compare example 1

① Picking a theme on first launch — an online bank's app

WhenOn first launch, the customer sees their account home (€2,418.36, the last three transactions) in a comparer; they drag the divider, then tap 'Keep dark' or 'Keep light'.
WhyBoth versions are visible at the same time, on the real account content: the customer chooses by comparing the same screen, not from memory between two screenshots.
Settings280 × 212 px comparer on the left, text and choice buttons on the right; both layers in display: block with positioned content (blue gradient balance card, transaction list); positive amounts in green, lighter on dark.
Light and dark preview of a newsletter — a regional newsroom's tool — Split Compare example 2

② Light and dark preview of a newsletter — a regional newsroom's tool

WhenBefore scheduling Thursday's 7 am letter, the newsroom drags the divider across the preview: in dark mode, the paper's navy logo almost vanishes into the background.
WhyAbout a third of subscribers read in dark mode: comparing on the same preview reveals the unreadable logo (1.6:1) before sending, where two separate previews hid it.
Settings290 × 212 px comparer; logo left in #1e3a5f in both layers to show the flaw; a checklist on the right with the contrast warning in orange.
Black or light worktop — a kitchen designer's configurator — Split Compare example 3

③ Black or light worktop — a kitchen designer's configurator

WhenFor the Durand kitchen quote, the customer compares the same kitchen in black granite and in white quartz (€340 more) by dragging the divider across the render.
WhyThe center split and the 'VS' knob frame the decision; the customer sees the same kitchen, at the same framing, in both finishes, and the line falls exactly where they put it.
Settings300 × 214 px comparer; two identical layers drawn in CSS (sage green cabinets, backsplash, hood) where only the worktop changes; labels moved to the bottom on a white backing to stay readable on the render.

How it works

The scene .th-split-scene (300 × 220 px, 16 px corners) overlaps two layers of its exact size with position: absolute; inset: 0: .th-split-dark (background #0f172a) below and .th-split-light (background #f8fafc) on top. Each holds the same component in the same place — here a 180 px card with a title and a progress bar, set at the bottom — and its label, 'Dark' top left, 'Light' top right. The light layer is clipped from the left up to the divider: clip-path: inset(0 0 0 var(--th-split-pos)), with --th-split-pos: 50% by default. At 50%, the card appears cut in two, half dark, half light: the comparison happens on a single object.

The gesture goes through a real <input type="range"> from 0 to 100, stretched over the whole scene (inset: 0, z-index 3), made invisible (opacity: 0, appearance: none, 2 px native thumb) but still active, with an ew-resize mouse cursor. The browser supplies everything else: press anywhere to jump there, drag, touch, keyboard. On each input event, the script writes the value into --th-split-pos. The visible divider is a scene-wide layer that ignores the pointer, shifted by translateX(var(--th-split-pos)): its ::before draws the 2 px violet-to-pink line on its left edge, and the 'VS' knob is centered on that edge, flanked by two small arrows that say it moves. No transition: the divider follows the gesture with no lag.

Measured in Chromium 151, Firefox 153 and WebKit 26: dragged from 50 to 20 then 85%, exact values and the knob center at 60 then 255 px; a single click at 30% jumps there; right arrow +1, Page Up +10, Home 0, End 100; in Chromium and Firefox, the Tab key reaches the slider and the white outline shows around the knob. By finger, in Chromium mobile emulation: a tap at 25% gives 25, a drag from 25 to 75% gives 75. The script (0.45 KB) walks through every .th-split-scene on the page: several comparers coexist. Without JavaScript, the scene keeps its fixed 50% cut. The effect compares two themes, it applies none: it does not read prefers-color-scheme and remembers nothing.

Accessibility

  • Native slider: an input type="range" announced as a slider (slider role) with its value from 0 to 100 and a name that says what is where: 'Divider: dark theme on the left, light theme on the right'. Keyboard measured in all three engines: arrows (1%), Page Up / Page Down (10%), Home and End. Keyboard focus draws a 2 px white outline around the knob, since the slider itself is invisible.
  • Duplicated content: both layers carry the same text; the light layer is aria-hidden, so a screen reader reads only one version. If the comparison carries information (a logo unreadable in dark mode, for instance), also state it in text nearby.
  • Motion: nothing moves on its own; the divider only moves under the finger, the mouse or the keyboard, with no animation. No reduced-motion rule is therefore needed.
  • Contrast and touch: 'Dark' label at 6.1:1, 'Light' at 4.6:1; the 'VS' knob is decorative. The slider covers the whole comparer: on a phone, a vertical swipe started on it may be captured by the slider instead of scrolling the page (not measured on a real device); leave some margin around it for scrolling.

Browser compatibility

CSS (clip-path: inset() with a variable, transform, pseudo-elements, slider styling via ::-webkit-slider-thumb and ::-moz-range-thumb, :focus-visible) + 0.45 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: the same values by mouse and keyboard, the same knob position to the pixel; touch measured in Chromium mobile emulation. Unprefixed clip-path: inset() exists since Chrome 55, Firefox 54 and Safari 9.1; custom properties since Chrome 49, Firefox 31 and Safari 9.1. The minimum versions come from inset: 0, which stacks the layers: Chrome and Edge 87, Firefox 66, Safari 14.1 (iOS 14.5).

Chrome 87+✓ Full
Firefox 66+✓ Full
Safari 14.1+✓ Full
Edge 87+✓ Full
Mobile iOS 14.5+✓ Full (touch not measured on a device)
Android Chrome 87+✓ Full (touch measured in emulation)

Without JavaScript, the scene stays cut at 50%: both themes side by side, the slider moves but the divider does not follow. Without clip-path, the light layer covers the whole scene.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="th-split-root">
  <div class="th-split-scene">
    <div class="th-split-half th-split-dark">
      <span class="th-split-label">…</span>
      <div class="th-split-mini-card">…</div>
    </div>
    <div class="th-split-half th-split-light" aria-hidden="true">
      <span class="th-split-label">…</span>
      <div class="th-split-mini-card">…</div>
    </div>
    <input class="th-split-range" type="range" min="0" max="100" step="1" value="50" aria-label="…">
    <div class="th-split-divider" aria-hidden="true"><span class="th-split-knob">VS</span></div>
  </div>
</div>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Split Compare, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Starting position (HTML — the slider's value, and CSS — --th-split-pos) 50 Put the same value in both places (value="30" and --th-split-pos: 30%) so it also holds without JavaScript.
Keyboard step (HTML — step) 1 (1% per arrow, 10% per page) step="5" makes the keyboard faster but moves the drag in 5% notches. Keep 1 for a smooth drag.
Layer content (HTML — .th-split-dark and .th-split-light) a 180 px card at the bottom Both layers must show the same content in the same place, otherwise the cut compares nothing. Two images of the same size (object-fit: cover) make a before / after photo.
Line and knob (CSS — .th-split-divider::before, .th-split-knob) 2 px #8b5cf6 → #ec4899 line, 'VS' knob Replace 'VS' with two chevrons or an icon; keep the knob at mid-height, where the thumb looks for it.
Colors and labels (CSS — both layer backgrounds, .th-split-label) #0f172a / #f8fafc, 'Dark' / 'Light' Also change the slider's name (aria-label) if the two sides are no longer themes.

FAQ

Because the browser supplies for free what a home-made handle must recode: mouse and finger dragging, click-to-jump, arrows, Page Up / Page Down, Home / End, and announcing the value to screen readers. The script only has to copy the value into --th-split-pos, in 0.45 KB. The slider is made invisible but keeps its focus: the knob shows the outline.
The effect compares, it applies nothing and does not read prefers-color-scheme. The position is in range.value at any time, but a position is not a choice: add two buttons ('Keep dark', 'Keep light') as in the first example, and apply and remember the chosen theme with your own code (a class on <html>, localStorage).
Partly. Both clip a layer with clip-path: inset() and draw a line on the border. Wipe Transition plays the change by itself in 0.6 s, from one theme to the other, and stops on the new one. Split Compare plays nothing: the user holds the border and leaves it wherever they want, for as long as it takes to compare. Choose Wipe Transition to switch, Split Compare to decide.