Page Turn Effect
A three-page light gray notebook (180 × 200 px) bound on the left, page 1 on top: on hover, page 1 turns, then page 2 0.45 s later, and page 3 stays to be read; turned pages settle on the left showing their back, like an open notebook. It closes in reverse order. Hover, Tab or a tap, with no JavaScript; under reduced motion, the pages fade out without turning.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →
3 usage examples



How it works
The root .fx-0015-root (#0a0a0f background) declares two settings: --page-duration: .8s (time for one page) and --page-gap: .45s (delay between two pages). The .demo-3d area is 360 × 200 px, the width of the open notebook (max-width: 100%), and .page-turn fills it with tabindex="0": it receives hover and focus. The .page-stack takes the right half, binding on its left edge, with perspective: 1500px; when closed, it is shifted half a page to the left (translateX(-50%)) so the notebook stays centered. Each .page turns around its left edge and carries two faces with hidden backs: .page-front, the front, and .page-back, the back, turned 180° in advance and shaded toward the binding.
On hover or focus (:focus-within), page 1 gets rotateY(-180deg) over 0.8 s; page 2 does the same 0.45 s later; page 3 does not move. Meanwhile the stack slides 90 px to the right, bringing the binding back to the middle of the frame. Measured in Chromium 151 and Firefox 153: page 1 passes vertical at 0.40 s and settles around 0.75 s, page 2 passes vertical at 0.85 s and settles around 1.2 s. At the end, page 3 reads on the right and the back of page 2 on the left (checked at the exact point under the pointer). When the pointer leaves, page 2 comes back first, then page 1: the notebook closes on page 1.
Why the stacking order changes halfway: the pages do not share one 3D space, so z-index decides which is on top (3, 2, 1 at rest). Yet a turned page must go under the next one once it lies on the left. So page 1 changes its z-index (3 → 1) exactly when it stands vertical, through a zero-length transition delayed by half the page time (z-index 0s calc(var(--page-duration) / 2)); when closing, it takes its place back as it passes vertical again. The delays are computed from the two variables: changing --page-duration or --page-gap keeps everything in sync.
Accessibility
- Keyboard and touch:
.page-turnhastabindex="0"and every:hoverrule is doubled by:focus-within. Tab opens the notebook to page 3, the next Tab closes it; a 2 px#a5b4fcring (:focus-visible) surrounds the stack for keyboard users. On a touchscreen, a tap opens it, a tap elsewhere closes it. Measured in Chromium 151, Firefox 153 and WebKit 26. A mouse click also gives focus: the notebook stays open until the next click elsewhere. - Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0015-rootremoves every rotation and slide (the notebook stays closed and centered); pages 1 then 2 fade out over 0.3 s, the second after--page-gap, and page 3 appears. Reading order is kept, without motion in space. - Screen readers: the three pages and their backs are in the HTML and read in order (page 1, its back, page 2…), notebook open or closed. Empty backs say nothing; if you put text on them, it is read right after its page.
- Contrast: semibold
#333text on light gray paper, from 8.19:1 (page 1, the darkest) to 11.9:1 (page 3).
Browser compatibility
CSS only (perspective, transform-style: preserve-3d, backface-visibility, CSS variables inside calc() for the delays, a z-index transition, :focus-within, :focus-visible), no JavaScript, no dependencies. Measured in Chromium 151 and Firefox 153: same page order when opening and closing, two independent copies on one page, fade without rotation under reduced motion. Playwright's WebKit (26.5) flattens all CSS 3D, even a reference cube: focus and touch are measured there, the 3D rendering was not checked on a real Safari.
Without 3D transforms, the pages do not turn: the notebook stays closed on page 1. In Safari before 15.4, add -webkit-backface-visibility: hidden to both faces, otherwise the front shows mirrored through the back. Without :focus-visible, the browser's default focus outline shows instead.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0015-root">
<div class="demo-3d">
<div class="page-turn" tabindex="0">
<div class="page-stack">
<div class="page">
<div class="page-front">… page 1 …</div>
<div class="page-back">… its back …</div>
</div>
… (pages 2 and 3, same structure)
</div>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Pace (CSS — --page-duration and --page-gap on .fx-0015-root) |
.8s and .45s | A gap shorter than half the duration sends two pages flying at once; a gap equal to the duration turns one page at a time. Every delay (rotations, slide, z-index changes) is computed from these two variables: only touch them. |
Size (CSS — width and height of .fx-0015-root .demo-3d) |
360 × 200 px | Each page is half the width and the full height. Keep max-width: 100%: on a 320 px screen, the pages shrink to 160 px wide instead of overflowing. |
Perspective (CSS — perspective of .page-stack) |
1500px | Smaller (800 px), the lifting page grows clearly as it comes toward you; larger, the leafing gets flatter. |
Paper (CSS — background of each .page-front by :nth-child, gradient of .page-back) |
#e8e8e8 → #d0d0d0, #f0f0f0 → #e0e0e0, #f8f8f8 → #f0f0f0; back #f2f2f2 → #d8d8d8 | The back's gradient darkens toward the binding: keep that shadow, it is what makes an open notebook readable. For cream paper, start from #fbf6ea. |
Notebook slide (CSS — transform of .page-stack) |
translateX(-50%) closed, none open | Replace translateX(-50%) with none for a notebook that does not slide: it then sits right of center, closed as well as open. |
FAQ
.page block and follow one simple rule, for page number k out of N. At rest: z-index N + 1 − k; return (closing) delayed by (N − 1 − k) × --page-gap, and z-index change at that delay plus half of --page-duration. On hover: rotation delayed by (k − 1) × --page-gap, z-index k halfway through its rotation. The last page does not turn; the return delay of .page-stack becomes (N − 2) × --page-gap. Rule checked with 4 pages in Chromium and Firefox: opening 1, 2, 3, 4, closing in reverse order.z-index) and replace the hover rules with a class set on each page, for instance .page.is-turned { transform: rotateY(-180deg) }, added and removed by "next page" and "previous page" buttons in JavaScript, which also update the turned page's z-index.