Page Turn
A real page turn: page A swings −160° around its left edge, inside a 1,200 px perspective, with its shadow and the book's spine, and uncovers page B lying underneath; the second press brings it back. CSS + 0.9 KB of JavaScript, target page at once 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 .pageturn-container (240 × 200 px) holds the perspective (perspective: 1200px). Both .pageturn-pages are stacked inside with inset: 0, rounded only on the free side (4px 16px 16px 4px), with transform-origin: left center: they turn around their left edge, the binding. Page A is on top (z-index: 2) and casts a shadow to the right; page B waits underneath (z-index: 1). A 4 px .pageturn-spine strip, above both, draws the spine.
When .active toggles, A turns to rotateY(-160deg) in 0.9 s (cubic-bezier(.4, 0, .2, 1)). The perspective makes the free edge grow as it comes toward the eye, then the page goes edge-on, around −84°, and stops being drawn: backface-visibility: hidden hides its reverse, and the .active rule also sets it to visibility: hidden with a 0.32 s delay, the moment it is edge-on, for engines that do not apply the former (the test WebKit is one). B then appears in full. B itself does not move. Measured in Chromium 151: 250 ms after the click, page A only covers 177 px of width on screen (189 px in Firefox 153); at 1.1 s it is at −160° and the state still holds three seconds later. Checked by full-width screen capture in Chromium, Firefox and WebKit 26.5: B alone after the click, nothing left of the binding, then A again after the second click, which brings the page back on top (it becomes visible again at 0.32 s, edge-on).
Past 90°, the page is no longer drawn: the final angle (−160°) therefore does not change the end image, it sets the path, that is how fast the page lifts and then vanishes edge-on. The script adds or removes .active on click, Enter or Space on every .pageturn-container of the page, keeps aria-pressed up to date and sets aria-hidden on the covered page; everything is prefixed pageturn-, nothing is exposed on window. A simpler variant, which swings a colored panel, also exists in the category.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, the rotation is removed and the next page shows at once (measured at 60 ms in Chromium and Firefox: page A already at −160°). - Keyboard and screen readers: the container has
role="button",tabindex="0"andaria-pressed; Enter and Space turn the page like a click (measured). The covered page getsaria-hidden="true": only the visible page is read. 2 px outline on keyboard focus. - Multi-page book: provide explicit "Previous page" and "Next page" buttons and announce the page number (
aria-live="polite"region); a turning page does not say where you are. - Page text: it swings with the page and becomes unreadable as soon as it lifts; that is intended, but do not put information there that must be read during the gesture.
Browser compatibility
CSS (perspective, rotateY, transform-origin, backface-visibility with the -webkit- prefix, delayed visibility, inset) + 0.9 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26.5: page turned, page B alone visible, checked by full-width screen capture (no mirrored reverse, WebKit included), state B held, return on the second click; keyboard and reduced motion measured in Chromium and Firefox. Each page turns on its own inside its parent's perspective, with no preserve-3d: the most widely supported form of 3D. inset sets the floor (Chrome 87, Firefox 66, Safari 14.1).
Without JavaScript, page A stays on screen. In an engine that ignores backface-visibility, the visibility switch still hides the turned page: its mirrored reverse does not show left of the binding.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="pageturn-stage">
<div class="pageturn-container" role="button" tabindex="0" aria-pressed="false">
<div class="pageturn-spine"></div>
<div class="pageturn-page pageturn-page-b" aria-hidden="true">…</div>
<div class="pageturn-page pageturn-page-a">…</div>
</div>
<span class="pageturn-hint">…</span>
</div>
The full HTML + CSS + JS of Page Turn, 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 |
|---|---|---|
Final angle (CSS — rotateY(-160deg)) |
−160° | The angle only changes the path: past 90°, the page is no longer drawn, so −120°, −160° and −175° give the same end image; the larger the angle, the sooner the page goes edge-on. For a barely lifted page that partly shows B, use −30° to −60° and remove visibility: hidden from the .active rule. For another angle beyond 90°, reset the visibility delay (0.32 s) to the moment the page goes edge-on. |
Perspective (CSS — the container's perspective) |
1,200 px | 800 px makes the free edge more dramatic as it comes toward the eye; 2,000 px gives an almost flat, subtle turn. |
Duration and curve (CSS — transition on .pageturn-page and .pageturn-page-a) |
0.9 s, cubic-bezier(.4, 0, .2, 1) |
0.6 s to leaf through quickly; cubic-bezier(.3, .1, .3, 1) lifts the page slowly then lets it drop. The visibility delay (0.32 s) follows the duration: set it to 0.21 s for 0.6 s. |
Page shadow (CSS — box-shadow on .pageturn-page-a) |
rgba(0, 0, 0, .3) 5px 0 20px |
It gives the paper's thickness on the page below; raise the blur to 30 px for thick paper, remove it for a flat look. |
Spine (CSS — .pageturn-spine) |
4 px of 8% white | A black-to-transparent gradient over 6 px mimics the hollow of an open book's binding. |
FAQ
z-index). Each click turns the top page still lying flat (an is-turned class applying rotateY(-160deg)); going back removes the class from the last turned page. Raise a page's z-index while it turns so it passes over the others.backface-visibility: hidden hides its reverse, and visibility: hidden, applied with a 0.32 s delay when the page goes edge-on, takes over in engines that ignore the former (in WebKit 26.5, without this switch, the reverse would stay visible, mirrored, left of the binding). That is what uncovers B with no extra JavaScript. To show a back (the left page of an open book), you need a back face inside the page and transform-style: preserve-3d, like a two-sided card, and remove the visibility switch.pointermove, turn the horizontal movement into an angle (0 to −160°) written to style.transform with no transition, then on release add or remove .active depending on whether the page went past −80°.