Wipe Transition
The new theme covers the old one from left to right in 0.6 s, cut by clip-path: inset(), while a 3 px glowing line follows the border to the pixel: you see before and after at the same time, and where the limit runs. A second press sends the front back to the left; a 150 ms fade when the user reduces motion.
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



How it works
The scene .th-wipe-scene (300 × 220 px, 16 px corners, overflow: hidden) overlaps two layers of its exact size with position: absolute; inset: 0: .th-wipe-dark (background #0c0a09) below, .th-wipe-light (background #fff7ed) on top, each with its own version of the title, line of text and chips, centered in a column. At rest, the light layer is clipped from the right across its whole width: clip-path: inset(0 100% 0 0). The .active class brings it back to inset(0) with transition: clip-path .6s cubic-bezier(.4, 0, .2, 1): its visible part grows from the left edge to the right edge, with nothing sliding — the text stays still, only the border moves.
The line is a third layer, .th-wipe-line, as wide as the scene and ignoring the pointer (pointer-events: none), whose ::before pseudo-element draws a 3 px violet-to-pink bar with a 12 px glow on its left edge. It goes from translateX(0) to translateX(100%) — 100% of its own width, so of the scene — with the same duration and curve as the clipping. The movement goes through transform, which the browser animates without recomputing layout. Measured every 80 ms: front and line at the same position to a tenth of a pixel in Chromium 151 and Firefox 153 (192.2 / 192.2 px at 240 ms); in WebKit 26, the line runs 1 to 11 px ahead of the front mid-course.
The script (0.6 KB) arms every .th-wipe-scene declared role="button": click, Enter and Space toggle .active and update aria-pressed. Removing the class plays everything backwards: the front heads back left with its line (measured 67 px in Chromium, 91 px in Firefox 300 ms after the second click). A scene without role="button" is not armed: that is the intended way to drive the switch from your own toggle, as in the examples below. At rest, the line stays visible along the left edge; once the switch is done, it leaves on the right, hidden by the scene. The effect does not read prefers-color-scheme and remembers nothing: you decide the starting state.
Accessibility
- Keyboard and role: the demo scene is a focusable
role="button"(tabindex="0"), named 'Light theme', witharia-pressed. Measured: reached with the Tab key in Chromium and WebKit, toggled by Enter and by Space in all three engines; a 2 px violet outline on keyboard focus. In a real interface, prefer a visible<button>that drives the scene (see the examples): a whole clickable panel is hard to guess. - Duplicated content: both layers carry the same text and the light layer is marked
aria-hidden. On the demo, the button's name covers the reading; if you drive the scene from a separate button, alternatearia-hiddenbetween the two layers on each switch, as the examples do. - Reduced motion: under
prefers-reduced-motion: reduce, no wipe and no line (hidden); the light layer arrives with a 150 ms opacity fade. Measured: opacity 0.39 (Chromium) and 0.22 (Firefox) 60 ms after the click, 1 at 300 ms. - Contrast: on the light theme, the line of text is
#78716c(4.5:1 on#fff7ed) and the chips 4.0:1; on the dark theme, the line is at 5.3:1. The glowing line is only a visual marker: no information depends on its color alone.
Browser compatibility
CSS (animated clip-path: inset(), transform, pseudo-element, :focus-visible) + 0.6 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: layers overlapping, same round trip, same keys; line/front sync exact in Chromium and Firefox, a 1 to 11 px gap in WebKit. Unprefixed clip-path with inset() and transitions exists since Chrome 55, Firefox 54 and Safari 12.1. The minimum versions come from inset: 0, which stacks the layers: Chrome and Edge 87, Firefox 66, Safari 14.1 (iOS 14.5).
Without clip-path, the light layer is never clipped: the scene stays light and only the line crosses it. Without JavaScript, the scene stays dark and does not react; setting active in the HTML serves the light theme directly.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="th-wipe-root">
<div class="th-wipe-scene" role="button" tabindex="0" aria-pressed="false" aria-label="Light theme">
<div class="th-wipe-dark">
<h3>…</h3><p>…</p>
<div class="th-wipe-chips">…</div>
</div>
<div class="th-wipe-light" aria-hidden="true">
<h3>…</h3><p>…</p>
<div class="th-wipe-chips">…</div>
</div>
<div class="th-wipe-line" aria-hidden="true"></div>
</div>
</div>
The full HTML + CSS + JS of Wipe Transition, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Duration and curve (CSS — transition on .th-wipe-light and .th-wipe-line) |
.6s cubic-bezier(.4, 0, .2, 1) | Change both values together, otherwise the line drifts away from the front. 0.9 s for a weighty page, 0.4 s for a quick switch. |
Wipe direction (CSS — resting clip-path and translateX) |
left to right: inset(0 100% 0 0) |
Right to left: inset(0 0 0 100%) at rest, and the line from translateX(100%) to translateX(0), drawn on the left edge. Top to bottom: inset(0 0 100% 0) and a horizontal line moved with translateY. |
Line (CSS — .th-wipe-line::before) |
3 px, #8b5cf6 → #ec4899 gradient, 12 px glow |
1 px with no glow for a sober look; 6 px for a display seen from afar. Removing .th-wipe-line gives a wipe with no marker. |
Colors of both themes (CSS — backgrounds of .th-wipe-dark / .th-wipe-light, then titles, lines, chips) |
#0c0a09 and #fff7ed |
Each layer has its own text rules; check 4.5:1 on each one's secondary line. |
Trigger (HTML — role="button" on the scene) |
the whole scene is the button | Remove role, tabindex and aria-pressed from the scene, and toggle .active from your own button: the script no longer touches the scene. |
Reduced motion (CSS — @media (prefers-reduced-motion: reduce) block) |
150 ms fade, line hidden | transition: none for an instant change. |
FAQ
translateX refers to the element's own width: a 3 px bar could only move 3 px for 100%. A layer as wide as the scene, drawing the bar on its left edge, moves exactly the width of the scene, whatever it is, and follows the front with no hard-coded pixel. Animating left would draw the same path, but recompute layout on every frame.aria-pressed="false": the state always follows the last click. If you would rather ignore clicks during the wipe, wait for the light layer's transitionend event before accepting the next one.