9 CSS Screen Transition Animation Effects
A CSS screen transition animation smooths every moment the screen changes — an image swap, a confirmed order, a form step — so the page glides from one state
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Blinds slat reveal
- 02 Polaroid swing-in
- 03 Grid↔list view switch
- 04 Main image slide swap
- 05 Filter chip slot-fill
- 06 Fly-to-cart add
- 07 Ticket-tear confirm
- 08 Wizard step slide
- 09 Diagonal scene swap
The nine are ordered by the moments a visitor actually meets on a live page. The first scene opens like window blinds (01), a photo swings in on a string (02), the browsing mode glides between grid and list (03), the featured image swaps in place (04), a picked filter chip flies into its slot (05), an added product arcs into the cart (06), a ticket tears apart at confirmation (07), a form slides step by step in the direction you move (08), and finally a whole scene splits diagonally and gives way to the next one (09).
| # | Name | What moves | JS |
|---|---|---|---|
| 01 | Blinds slat reveal | 6 slats scaleY cascade |
none |
| 02 | Polaroid swing-in | card rotate damped swing |
none |
| 03 | Grid↔list view switch | cards translate positions |
none |
| 04 | Main image slide swap | scenes translateX crossing |
none |
| 05 | Filter chip slot-fill | chip translate flight |
none |
| 06 | Fly-to-cart add | dot double-translate arc |
none |
| 07 | Ticket-tear confirm | two halves rotate apart |
none |
| 08 | Wizard step slide | panels translateX ±100% |
none |
| 09 | Diagonal scene swap | skew panels sweeping |
none |
01Blinds slat reveal
When the page opens, six horizontal blind slats covering a bright headline panel fold away from the top one after another and uncover the text beneath. Instead of a hero headline flashing in all at once, it settles open slat by slat.
.reveal__slat {
transform-origin: top center;
transform: scaleY(1);
animation: slat-open $duration $easing both;
}
@keyframes slat-open {
from { transform: scaleY(1); }
to { transform: scaleY(0); }
}
// each slat waits 90ms longer than the one above it
.reveal__slat:nth-child(2) { animation-delay: 90ms; }
.reveal__slat:nth-child(3) { animation-delay: 180ms; }
02Polaroid swing-in
A polaroid photo card hung from a single pin drops in tilted, sways left and right a few times, and comes to rest perfectly straight. Dropped onto a gallery intro or a travel-log thumbnail, it gives a static photo page a physical, handmade warmth.
.swing {
transform-origin: top center; // the pin is the rotation axis
animation: swing-in $duration $easing both;
}
@keyframes swing-in {
0% { transform: translateY(-70px) rotate(18deg); opacity: 0; }
38% { transform: rotate(-11deg); }
56% { transform: rotate(7deg); }
72% { transform: rotate(-4deg); }
100% { transform: rotate(0deg); }
}
03Grid↔list view switch
The same three product cards glide between a two-column grid and a single-column list without ever leaving the frame. The toggle knob answers first and the cards follow, so the screen feels alive the instant you touch it.
.vs__card { transition: transform $duration $easing; }
// percentages are relative to the card's own size — grid: one column to the right
.vs__card--2 { transform: translate(calc(100% + 8px), 0); }
.vs__card--3 { transform: translate(0, calc(100% + 10px)); }
// list: the same cards stack one row at a time
#vs-list:checked ~ .vs__board .vs__card--2 { transform: translate(0, calc(100% + 10px)); }
#vs-list:checked ~ .vs__board .vs__card--3 { transform: translate(0, calc(200% + 20px)); }
04Main image slide swap
Picking a thumbnail slides the current image out to the left while the new one enters from the right into the same frame. On a product page this turns an abrupt image swap into a small, flowing exchange that makes browsing feel rewarding.
.gal__scene {
position: absolute; inset: 0;
transform: translateX(110%); // waiting off-stage right
transition: transform $duration $easing, opacity $duration $easing;
}
#gal-2:checked ~ .gal__frame .gal__scene--2 { transform: translateX(0); }
#gal-2:checked ~ .gal__frame .gal__scene--1 { transform: translateX(-110%); } // exits left
05Filter chip slot-fill
Picking a color chip sends that chip flying from its row up into an empty outlined slot above, landing at exactly the same size. The choice registers as a physical object moving, not as text changing, which reads instantly on shopping screens.
.pick__chip { transition: transform $duration $easing; }
// each chip has a different distance to the slot — chip 2 travels 96px left, 76px up
#pick-2:checked ~ .pick__row .pick__chip--2 {
transform: translate(-96px, -76px);
z-index: 2; // the flying chip floats above the row
}
#pick-2:checked ~ .pick__slot { border-color: rgba(255,255,255,.8); }
06Fly-to-cart add
Pressing Add sends one product-colored dot flying on an arc from the product card to the cart icon in the corner, fading away as it lands while the count badge bounces. Because the departure and arrival points share one screen, the visitor can follow their own action all the way to its destination.
.cart__fly { transition: transform $duration $easing; } // outer element: x at constant speed
.cart__fly-y { transition: transform $duration $easing; } // inner element: y on an eased curve
// landing coordinates come from measured getBoundingClientRect values
#cart-add:checked ~ .cart__fly { transform: translateX(273px); opacity: 0; }
#cart-add:checked ~ .cart__fly .cart__fly-y { transform: translateY(-71px); }
#cart-add:checked ~ .cart__bag .cart__badge { transform: scale(1.35); }
07Ticket-tear confirm
At the moment of confirmation the dashed line down the middle of the ticket lights up, and the left and right halves tilt away in opposite directions and exit the frame as two torn stubs. For irreversible moments like placing an order or checking in, this seals the action far more firmly than a text change.
.tear__half { transition: transform $duration $easing, opacity $duration $easing; }
#tear-ok:checked ~ .tear__ticket .tear__half--l {
transform: translate(-52px, 26px) rotate(-9deg); // left half: down-left
opacity: 0;
}
#tear-ok:checked ~ .tear__ticket .tear__half--r {
transform: translate(44px, 30px) rotate(8deg); // right half: down-right
opacity: 0;
}
08Wizard step slide
Moving to the next step slides the current panel out left and brings the new one in from the right, and going back reverses the direction exactly. You never have to read an arrow to know where you are in the flow, so multi-step forms stop feeling like a maze.
.wiz__panel { transition: transform $duration $easing, opacity $duration $easing; }
.wiz__panel--2 { transform: translateX(100%); opacity: 0; } // step 2's home side: right
// going back sends step 1 to its left home and step 2 back to the right — direction flips
#wiz-2:checked ~ .wiz__window .wiz__panel--1 { transform: translateX(-100%); opacity: 0; }
#wiz-2:checked ~ .wiz__window .wiz__panel--2 { transform: translateX(0); opacity: 1; }
#wiz-1:checked ~ .wiz__window .wiz__panel--1 { transform: translateX(0); opacity: 1; }
09Diagonal scene swap
Two panels crossing the frame diagonally slide past in the same direction, one stepping in a beat behind the other, and sweep away to uncover the next scene hidden behind them in one motion. Season or campaign scenes on a landing page can change dramatically without a single reload.
.scene__wipe {
top: -20%; bottom: -20%; left: -45%;
width: 190%;
transform: skewX(-14deg) translateX(-115%); // a panel laid diagonally
transition: transform $duration $easing;
}
#scene-swap:checked ~ .scene__frame .scene__wipe { transform: skewX(-14deg) translateX(115%); }
.scene__inner { transition: opacity $dur-quick linear calc(#{$duration} / 2); } // swap at midpoint
Where does it break — one trap
The first version of the fly-to-cart dot clearly moved, but it landed somewhere off target. I had eyeballed the distance from the card to the cart and typed translateX(247px); measuring the real coordinates with Playwright showed the badge center sat farther right than expected, and the dot was vanishing into thin air 26px short of it. Remeasuring both centers with getBoundingClientRect and correcting to translateX(273px) and translateY(-71px) made the dot land dead center on the badge. The same inspection caught the pseudo-element that draws the basket handle: it was anchored to the wrong parent and floating loose in a corner of the stage, fixed with one position: relative line. Flight animations should never be aimed by eye — measure the coordinates. The password that unlocks these nine files is 2aq7sbjt, published only after exactly this kind of verification.
Accessibility
All nine effects turn off motion under prefers-reduced-motion: reduce and keep the final state visible. 01 shows the headline with the blinds already folded away, 02 hangs the photo straight with no swing, 03, 04, and 08 swap cards, scenes, and panels instantly with no transition, 05 seats the chip in the slot immediately, 06 keeps the dot hidden and only bumps the badge, 07 skips the tearing and removes the ticket at once, and 09 swaps the scene with no panels sweeping through. Every click target is a real radio or checkbox wired to a visible label, so keyboard and screen-reader users can operate them too, and purely decorative elements are marked aria-hidden. Why transform-based motion stays smooth is documented at MDN's transform reference, and what the reduced-motion media query means at MDN's prefers-reduced-motion page. For transitions between whole pages, see 9 CSS Page Transitions, and for tab panels, the tab menu transition collection.
FAQ
Do I really need zero lines of JavaScript?
These nine run on CSS alone. Anything click-driven hides a radio or checkbox on the page and lets CSS follow the :checked state, so they work the moment you paste them in. The React versions use useState to manage that state instead.
What do I change to drop one into my own site?
Adjust the class names of the moving parts and the distance values to match your own layout. The chip flight in 05 and the cart flight in 06 depend on where your chips, cards, and cart icon actually sit, so measure the element coordinates in your browser's developer tools and enter those numbers.
Can the View Transitions API do the same thing?
The View Transitions API is a browser feature for moments when an entire document or large region changes, not for small in-page state changes like these, and its support still varies between browsers. These nine effects use only transform and opacity so they run identically everywhere.