Flipping ABC Book
A cute alphabet book that flips from A to B to C with playful page motion and tiny learning doodles.
← a little library, in 50 minutes.
what you'll build
Here's the plan
A cute open alphabet book in SVG that flips from A to B to C. Each letter gets its own page group, a tiny doodle friend, and one smooth 2D page-flip animation built from scaleX and skewY.
You’ll learn
- Layering an open book from cover, spine, and pages
- Hiding and revealing SVG groups with opacity and scaleX
- Using transform-origin to make a page flip around its spine edge
- Staggering a multi-stage CSS animation so three pages take turns
- Wrapping decorative motion in prefers-reduced-motion
- Skill level
- Advanced
- Time
- ~50 min
- Tools
- Just a browser
- Code type
- SVG + CSS
- Lines
- ~95
the steps
Build it, one change at a time
Build the open book base
Start with a warm sketchbook background, a soft shadow, and the open book's cover and spine. The left page is blank for now; the right side will receive the flipping pages in the next step.
<!-- book cover and spine behind the pages --> <path d="M24 66 Q24 58 32 58 L114 58 L116 58 L198 58 Q206 58 206 66 L206 174 Q206 182 198 182 L32 182 Q24 182 24 174 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="5" stroke-linejoin="round"/> <!-- central spine crease --> <rect x="112" y="58" width="6" height="124" rx="2" fill="#2B3A55" opacity="0.15" stroke="none"/> <!-- left page (blank) --> <rect x="28" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>an open book cover with a blank left page — the right side is waiting for its pages.
Add page layers and alphabet content
Add three right-hand page groups, each with the same page shape, tab, curled corner, and a large letter. B and C start hidden with an inline opacity so only A shows until the animation takes over.
<!-- right-hand page layers --> <g class="page page-a"> <rect x="120" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> <rect x="146" y="58" width="30" height="10" rx="3" fill="#FFD84D" stroke="#2B3A55" stroke-width="3.5"/> <text x="161" y="132" text-anchor="middle" dominant-baseline="middle" font-size="58" font-weight="800" fill="#2B3A55">A</text> </g> <g class="page page-b" opacity="0"> <!-- same page shape + letter B --> </g> <g class="page page-c" opacity="0"> <!-- same page shape + letter C --> </g>the A page appears; B and C are stacked but hidden for now.
Add tiny A/B/C doodles
Give each letter a tiny doodle friend — an apple for A, a butterfly for B, a sleepy cloud for C — and scatter a pencil, star, and bookmark around the book. These accents stay still until the final animation step.
<!-- tiny apple on the A page --> <g transform="translate(154, 154)"> <circle cx="0" cy="2" r="9" fill="#FF6B5E" stroke="#2B3A55" stroke-width="3"/> <path d="M0 -7 Q3 -14 8 -11" fill="none" stroke="#2B3A55" stroke-width="2.5"/> <path d="M6 -12 Q12 -15 10 -8 Z" fill="#A8E6CF" stroke="#2B3A55" stroke-width="2"/> </g> <!-- tiny butterfly on the B page --> <g transform="translate(146, 154)"> ... </g> <!-- tiny cloud on the C page --> <g transform="translate(146, 154)"> ... </g>an apple, a butterfly, and a tiny cloud move in to join their letters.
Set the page transform origin
A page flips around its spine edge, not its center. Set transform-origin on the left edge of each right-hand page so scaleX collapses the page neatly toward the spine when the animation runs.
.flipping-abc-book .page { transform-origin: 120px 120px; } .flipping-abc-book .page-b, .flipping-abc-book .page-c { opacity: 0; transform: scaleX(0.05) skewY(0deg); }nothing moves yet, but each page now knows its hinge is at the spine edge.
Animate the first flip
Use scaleX to collapse the page and skewY to fake the bending paper. The A page exits while the B page enters at the same time, creating the first half of the flip illusion.
@media (prefers-reduced-motion: no-preference) { .flipping-abc-book .page-a { animation: flip-a 4s ease-in-out infinite; } .flipping-abc-book .page-b { animation: flip-b 4s ease-in-out infinite; } } @keyframes flip-a { 0%, 40%, 90%, 100% { opacity: 1; transform: scaleX(1) skewY(0deg); } 45% { opacity: 0; transform: scaleX(0.06) skewY(-12deg); } 50%, 85% { opacity: 0; transform: scaleX(0.06) skewY(0deg); } } @keyframes flip-b { 0%, 40%, 90%, 100% { opacity: 0; transform: scaleX(0.06) skewY(0deg); } 45% { opacity: 1; transform: scaleX(0.06) skewY(-12deg); } 50%, 85% { opacity: 1; transform: scaleX(1) skewY(0deg); } }A flips away and B flips in — the book is alive.
Stage the letter changes
Add the C page to the cycle. Now the animation has three acts: A exits and B enters, B exits and C enters, C exits and A re-enters. The timing stays symmetrical so the loop feels like one endless page turn.
@media (prefers-reduced-motion: no-preference) { .flipping-abc-book .page-a { animation: flip-a 6s ease-in-out infinite; } .flipping-abc-book .page-b { animation: flip-b 6s ease-in-out infinite; } .flipping-abc-book .page-c { animation: flip-c 6s ease-in-out infinite; } } @keyframes flip-a { 0%, 12.5%, 75%, 100% { opacity: 1; transform: scaleX(1) skewY(0deg); } 18.75% { opacity: 0; transform: scaleX(0.06) skewY(-12deg); } 25%, 62.5% { opacity: 0; transform: scaleX(0.06) skewY(0deg); } 68.75% { opacity: 1; transform: scaleX(0.06) skewY(12deg); } } @keyframes flip-b { 0%, 12.5%, 50%, 100% { opacity: 0; transform: scaleX(0.06) skewY(0deg); } 18.75% { opacity: 1; transform: scaleX(0.06) skewY(-12deg); } 25%, 37.5% { opacity: 1; transform: scaleX(1) skewY(0deg); } 43.75% { opacity: 0; transform: scaleX(0.06) skewY(12deg); } } @keyframes flip-c { 0%, 37.5%, 75%, 100% { opacity: 0; transform: scaleX(0.06) skewY(0deg); } 43.75% { opacity: 1; transform: scaleX(0.06) skewY(-12deg); } 50%, 62.5% { opacity: 1; transform: scaleX(1) skewY(0deg); } 68.75% { opacity: 0; transform: scaleX(0.06) skewY(12deg); } }all three letters take turns flipping across the page.
Loop and respect motion
Wrap the page-flip animations in a prefers-reduced-motion guard, then add a tiny twinkling star and a gently wiggling bookmark. Now the book loops forever when motion is welcome, and stays perfectly still when it isn't.
@media (prefers-reduced-motion: no-preference) { .flipping-abc-book .page-a { animation: flip-a 6s ease-in-out infinite; } .flipping-abc-book .page-b { animation: flip-b 6s ease-in-out infinite; } .flipping-abc-book .page-c { animation: flip-c 6s ease-in-out infinite; } .flipping-abc-book .sparkle { animation: star-twinkle 2.2s ease-in-out infinite; } .flipping-abc-book .bookmark { animation: bookmark-wiggle 3s ease-in-out infinite; } } @keyframes star-twinkle { 0%, 100% { transform: scale(0.85) rotate(0deg); opacity: 0.7; } 50% { transform: scale(1.15) rotate(15deg); opacity: 1; } } @keyframes bookmark-wiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(4deg); } 75% { transform: rotate(-4deg); } }the finished flipping ABC book — playful, wiggly, and reduced-motion friendly.
the complete code
Everything, in one place
Skipped straight to the end? Welcome. Copy the whole thing, download it, or open it in the Playground to start remixing.
<svg class="flipping-abc-book" viewBox="0 0 230 210" width="230" height="210" role="img" aria-label="Animated flipping ABC book">
<!-- soft sketchbook background -->
<rect x="0" y="0" width="230" height="210" fill="#FFF8EF" stroke="none"/>
<!-- soft shadow under the book -->
<ellipse cx="115" cy="194" rx="70" ry="7" fill="#2B3A55" opacity="0.12" stroke="none"/>
<!-- book cover behind the pages -->
<path d="M24 66 Q24 58 32 58 L114 58 L116 58 L198 58 Q206 58 206 66 L206 174 Q206 182 198 182 L32 182 Q24 182 24 174 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="5" stroke-linejoin="round"/>
<!-- central spine crease -->
<rect x="112" y="58" width="6" height="124" rx="2" fill="#2B3A55" opacity="0.15" stroke="none"/>
<!-- left page (blank) -->
<rect x="28" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<!-- right-hand page layers with doodles -->
<g class="page page-a">
<!-- page paper, tab, and curled corner -->
<rect x="120" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="146" y="58" width="30" height="10" rx="3" fill="#FFD84D" stroke="#2B3A55" stroke-width="3.5"/>
<path d="M182 176 L190 176 L182 164 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="3" stroke-linejoin="round"/>
<text x="161" y="132" text-anchor="middle" dominant-baseline="middle" font-size="58" font-weight="800" fill="#2B3A55" font-family="system-ui, sans-serif">A</text>
<!-- tiny apple -->
<g transform="translate(154, 154)">
<circle cx="0" cy="2" r="9" fill="#FF6B5E" stroke="#2B3A55" stroke-width="3"/>
<path d="M0 -7 Q3 -14 8 -11" fill="none" stroke="#2B3A55" stroke-width="2.5"/>
<path d="M6 -12 Q12 -15 10 -8 Z" fill="#A8E6CF" stroke="#2B3A55" stroke-width="2"/>
</g>
</g>
<g class="page page-b" opacity="0">
<!-- page paper, tab, and curled corner -->
<rect x="120" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="146" y="58" width="30" height="10" rx="3" fill="#FFD84D" stroke="#2B3A55" stroke-width="3.5"/>
<path d="M182 176 L190 176 L182 164 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="3" stroke-linejoin="round"/>
<text x="161" y="132" text-anchor="middle" dominant-baseline="middle" font-size="58" font-weight="800" fill="#2B3A55" font-family="system-ui, sans-serif">B</text>
<!-- tiny butterfly -->
<g transform="translate(146, 154)">
<ellipse cx="0" cy="0" rx="3" ry="10" fill="#2B3A55" stroke="none"/>
<path d="M-2 -6 Q-14 -18 -14 -4 Q-8 4 -2 2 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2.5"/>
<path d="M2 -6 Q14 -18 14 -4 Q8 4 2 2 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2.5"/>
<path d="M-2 -10 Q-6 -18 -4 -22" fill="none" stroke="#2B3A55" stroke-width="2" stroke-linecap="round"/>
<path d="M2 -10 Q6 -18 4 -22" fill="none" stroke="#2B3A55" stroke-width="2" stroke-linecap="round"/>
</g>
</g>
<g class="page page-c" opacity="0">
<!-- page paper, tab, and curled corner -->
<rect x="120" y="64" width="82" height="112" rx="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="146" y="58" width="30" height="10" rx="3" fill="#FFD84D" stroke="#2B3A55" stroke-width="3.5"/>
<path d="M182 176 L190 176 L182 164 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="3" stroke-linejoin="round"/>
<text x="161" y="132" text-anchor="middle" dominant-baseline="middle" font-size="58" font-weight="800" fill="#2B3A55" font-family="system-ui, sans-serif">C</text>
<!-- tiny cloud -->
<g transform="translate(146, 154)">
<path d="M-14 2 Q-16 -10 -4 -10 Q0 -16 10 -12 Q16 -6 12 2 Q8 8 -2 6 Q-10 8 -14 2 Z" fill="#E6EFFF" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round"/>
</g>
</g>
<!-- tiny pencil -->
<g transform="translate(30, 36) rotate(-18)" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round">
<rect x="0" y="0" width="10" height="34" rx="2" fill="#FFD84D"/>
<rect x="0" y="-8" width="10" height="8" rx="2" fill="#FFB3AD"/>
<rect x="0" y="-2" width="10" height="3" fill="#FFFDF7"/>
<path d="M0 34 L5 42 L10 34 Z" fill="#2B3A55"/>
</g>
<!-- tiny star sparkle -->
<g class="sparkle" transform="translate(200, 44)" fill="#FFD84D" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round">
<path d="M0 -10 L2 -2 L10 0 L2 2 L0 10 L-2 2 L-10 0 L-2 -2 Z"/>
</g>
<!-- bookmark ribbon -->
<g class="bookmark" transform="translate(176, 176)" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round">
<path d="M0 0 L0 24 L8 18 L16 24 L16 0 Z" fill="#FF6B5E"/>
</g>
</svg>
tips & gotchas
Mistakes we actually made
If the page content drifts during the flip, check the transform-origin sits on the spine edge (around the left side of each right-hand page), not the center of the SVG.
Use opacity: 0 during the hidden part of the cycle so the next page doesn't peek through the spine while it is still scaled flat.
skewY gives the page a bending illusion, but keep it small (around 12 degrees) — too much looks like a funhouse mirror.
Set all three page animations to the same duration and easing so the hand-offs between A, B, and C stay synchronized.
make it yours
Remix it
- New alphabeteasy
Swap the letters for D, E, F and draw new tiny doodles to match each one.
- Slower readereasy
Bump the animation duration from 6s to 10s so each letter stays readable longer.
- One-shot flipmedium
Remove the infinite keyword and add animation-fill-mode: forwards so the book lands on C and stays there.
- Sound effectsmedium
Use a small JS snippet to toggle a class on animation iteration, then play a tiny page-turn pop.
challenge extension
Same book, new story: add a fourth page with the letter D and its own doodle, then extend the keyframes so the book flips A → B → C → D and back to A.