Objects have weight. Shadows show depth. Movements are snappy and decisive.
Explore 54 animationsThe motion system is built on duration and easing tokens. Every animation references these — never raw values.
linear())linear() function. These produce physically accurate spring motion impossible with cubic-bezier alone.Color changes, opacity fades, border transitions. Anything that responds immediately and decelerates naturally.
Toggle switches, checkboxes, small interactive elements. Creates playful overshooting that adds personality.
Entrance animations, page transitions, modal reveals. Smooth deceleration creates a natural, controlled feel.
Hard stops, dismissals, and stamp-like impacts. Decelerates sharply for a weighty, definitive feel.
Springy overshoots and playful entrances. Pulls back before springing forward for lively, energetic motion.
Layout shifts that cause content reflow, font-size changes, or anything that blocks user interaction. Keep the thread clear.
Objects arrive with weight and leave with intention. Entrances and exits are natural pairs — choose one of each.
Scale 1.4 to 1 + Y drop, shadow appears on impact
@keyframes stamp-in {
0% { transform: scale(1.4) translateY(-20px); box-shadow: 0 0 0 transparent; opacity: 0; }
60% { opacity: 1; }
100% { transform: scale(1) translateY(0); box-shadow: var(--shadow-md); opacity: 1; }
}
.anim-stamp-in { animation: stamp-in 360ms var(--ease-bounce) both; }Falls from above with slight rotation, bounces at landing
@keyframes drop-in {
0% { transform: translateY(-60px) rotate(2deg); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(0) rotate(0deg); opacity: 1; }
}
.anim-drop-in { animation: drop-in 360ms var(--ease-bounce) both; }Slides from left with rotation, corrects sharply
@keyframes slide-slam {
0% { transform: translateX(-100px) rotate(-3deg); opacity: 0; }
100% { transform: translateX(0) rotate(0deg); opacity: 1; }
}
.anim-slide-slam { animation: slide-slam 360ms var(--ease-out) both; }Punches up from below past resting point, then settles
@keyframes punch-up {
0% { transform: translateY(40px); opacity: 0; }
70% { transform: translateY(-8px); }
100% { transform: translateY(0); opacity: 1; }
}
.anim-punch-up { animation: punch-up 240ms var(--ease-bounce) both; }ScaleY from top, paper-unfolding feel
@keyframes unfold {
0% { transform: scaleY(0); opacity: 0; }
100% { transform: scaleY(1); opacity: 1; }
}
.anim-unfold { animation: unfold 240ms var(--ease-smooth) both; transform-origin: top; }Simple drop-in with 60ms stagger per child
@keyframes brick-stack {
0% { transform: translateY(20px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
.anim-brick-stack { animation: brick-stack 160ms var(--ease-out) both; }Scale 0 to 1.15 to 1 with radius morph (circle to rect)
@keyframes pop-stamp {
0% { transform: scale(0); border-radius: 50%; opacity: 0; }
60% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(1); border-radius: var(--radius-md); opacity: 1; }
}
.anim-pop-stamp { animation: pop-stamp 360ms var(--ease-bounce) both; }Clip-path wipe from left to right
@keyframes shutter-reveal {
0% { clip-path: inset(0 100% 0 0); }
100% { clip-path: inset(0 0 0 0); }
}
.anim-shutter-reveal { animation: shutter-reveal 360ms var(--ease-out) both; }Thrown right with 8deg rotation, fades out
@keyframes fling-right {
0% { transform: translateX(0) rotate(0deg); opacity: 1; }
100% { transform: translateX(200px) rotate(8deg); opacity: 0; }
}Pressed into surface, shadow collapses, fades
@keyframes press-away {
0% { transform: scale(1); box-shadow: var(--shadow-md); opacity: 1; }
100% { transform: scale(0.9); box-shadow: 0 0 0 transparent; opacity: 0; }
}Scale down + alternating rotation, like crumpling paper
@keyframes crumple {
0% { transform: scale(1) rotate(0deg); opacity: 1; }
40% { transform: scale(0.6) rotate(-3deg); }
100% { transform: scale(0.3) rotate(0deg); opacity: 0; }
}Hinges from bottom edge via perspective rotateX
@keyframes trapdoor {
0% { transform: perspective(600px) rotateX(0deg); opacity: 1; }
100% { transform: perspective(600px) rotateX(90deg); opacity: 0; }
}Scale to 0, instant and decisive
@keyframes snap-shrink {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(0); opacity: 0; }
}Detaches from surface, accelerates downward with tumble
@keyframes gravity-fall {
0% { transform: translateY(0) rotate(0deg); box-shadow: var(--shadow-md); opacity: 1; }
100% { transform: translateY(300px) rotate(12deg); box-shadow: none; opacity: 0; }
}| Component | Entrance | Exit | Feedback |
|---|---|---|---|
| Toast | slide-slam | fling-right | confirm-thud |
| Modal | stamp-in | press-away | — |
| Card | brick-stack | trapdoor | spotlight |
| Alert | info-slide | press-away | error-jolt |
How objects respond to users. Attention-grabbing shakes, system feedback, and the micro-interactions that make UI feel alive.
Front-loaded horizontal shake, settles fast
@keyframes knock {
0% { transform: translateX(0); } 15% { transform: translateX(12px); }
30% { transform: translateX(-10px); } 50% { transform: translateX(6px); }
70% { transform: translateX(-3px); } 100% { transform: translateX(0); }
}Scale up (shadow grows), compress (shadow collapses), settle
@keyframes stamp-pulse {
0% { transform: scale(1); box-shadow: var(--shadow-md); }
35% { transform: scale(1.08); box-shadow: var(--shadow-lg); }
65% { transform: scale(0.96); box-shadow: var(--shadow-sm); }
100% { transform: scale(1); box-shadow: var(--shadow-md); }
}Small alternating rotation, eager tremor
@keyframes wiggle {
0% { transform: rotate(0deg); } 15% { transform: rotate(3deg); }
30% { transform: rotate(-3deg); } 50% { transform: rotate(2deg); }
70% { transform: rotate(-1deg); } 100% { transform: rotate(0deg); }
}Shadow grows + focus ring blooms, then retreats
@keyframes spotlight {
0% { box-shadow: var(--shadow-md); }
50% { box-shadow: 0 0 0 6px var(--accent-primary); }
100% { box-shadow: var(--shadow-md); }
}Vertical jump with bounce settle
@keyframes headbutt {
0% { transform: translateY(0); } 25% { transform: translateY(-16px); }
50% { transform: translateY(0); } 70% { transform: translateY(-6px); }
100% { transform: translateY(0); }
}Marching ants with chunky 8px dashes
/* Uses repeating-linear-gradient for marching ants */
.anim-border-march { animation: border-march 800ms linear infinite; }Multi-bounce landing with green flash
@keyframes confirm-thud {
0% { transform: scale(0); opacity: 0; }
50% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}Horizontal shake + danger background flash
@keyframes error-jolt {
0% { transform: translateX(0); }
20% { transform: translateX(8px); background: var(--accent-danger-subtle); }
40% { transform: translateX(-8px); }
100% { transform: translateX(0); }
}Gentle vertical bob with slight tilt
@keyframes warning-bob {
0% { transform: translateY(0) rotate(0deg); }
20% { transform: translateY(-6px) rotate(1deg); }
100% { transform: translateY(0) rotate(0deg); }
}Slide-in from left with subtle overshoot
@keyframes info-slide {
0% { transform: translateX(-30px); opacity: 0; }
70% { transform: translateX(4px); opacity: 1; }
100% { transform: translateX(0); opacity: 1; }
}Three dots bouncing with 120ms stagger
@keyframes loading-slam {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-20px); }
}ScaleX fill that overshoots 2% and springs back
@keyframes progress-fill-slam {
0% { transform: scaleX(0); }
85% { transform: scaleX(1.02); }
100% { transform: scaleX(1); }
}Hover lifts, active slams in. Transition-based.
.micro-btn-press:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-md); }
.micro-btn-press:active { transform: translate(2px, 2px); box-shadow: 0 0 0 var(--text-primary); }Knob stretches horizontally during transit, squishes on arrival
@keyframes toggle-thwack {
0% { transform: translateX(0) scaleX(1); }
40% { transform: translateX(12px) scaleX(1.3); }
100% { transform: translateX(24px) scaleX(1); }
}Container pops in with overshoot + checkmark
@keyframes check-pop {
0% { transform: scale(0); opacity: 0; }
60% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}Subtle 3D perspective tilt on hover
.micro-hover-tilt:hover {
transform: perspective(600px) rotateY(4deg) rotateX(-2deg);
}Scale up + rotate + shadow max on drag start
@keyframes drag-lift {
0% { transform: scale(1) rotate(0deg); box-shadow: var(--shadow-md); }
100% { transform: scale(1.05) rotate(2deg); box-shadow: var(--shadow-lg); }
}Shadow shifts to accent 4px offset on focus
.micro-input-focus:focus {
box-shadow: 4px 4px 0 var(--accent-primary);
}Gentle opacity pulse on resize handles
@keyframes resize-grip-pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}Solid-border circle expanding from click point
@keyframes ripple-stamp {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(4); opacity: 0; }
}Background motion and typography in motion. Subtle animations that add life without demanding attention.
Small dot orbiting an element with counter-rotation
@keyframes hover-orbit {
0% { offset-distance: 0%; transform: rotate(0deg); }
100% { offset-distance: 100%; transform: rotate(-360deg); }
}Shadow breathes between sm and md
@keyframes breathe-shadow {
0% { box-shadow: var(--shadow-sm); }
100% { box-shadow: var(--shadow-md); }
}Swings +/-3deg from top-center origin
@keyframes pendulum {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(3deg); }
75% { transform: rotate(-3deg); }
}Gentle 4px vertical bounce
@keyframes idle-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}Horizontal text scroll, marquee-style
@keyframes ticker-scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
}Character-by-character reveal with blinking cursor
.anim-typewriter-mono {
overflow: hidden; white-space: nowrap; width: 0;
border-right: 2px solid var(--text-primary);
animation: typewriter-mono 2s steps(20, end) forwards,
typewriter-cursor-blink 700ms steps(1) infinite;
}Per-character drop-in with 40ms stagger
@keyframes text-stamp {
0% { transform: translateY(-20px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}Characters cycle through random glyphs before resolving
// JS-driven: randomize each character for ~400ms
// then resolve left-to-right with 30ms stagger3px underline draws left-to-right on hover
.text-underline-draw::after {
content: ''; position: absolute; bottom: 0; left: 0;
width: 100%; height: 3px; background: var(--accent-primary);
transform: scaleX(0); transform-origin: left;
transition: transform 240ms var(--ease-out);
}
.text-underline-draw:hover::after { transform: scaleX(1); }Gold background sweeps across text
@keyframes highlight-sweep {
0% { background-size: 0% 100%; }
100% { background-size: 100% 100%; }
}Real-world forces and state changes. Springs, gravity, morphing shapes, and flipping cards.
Damped spring oscillation with decreasing overshoots
@keyframes spring-snap {
0% { transform: translateX(-100px); }
40% { transform: translateX(8px); }
60% { transform: translateX(-4px); }
80% { transform: translateX(2px); }
100% { transform: translateX(0); }
}Accelerating fall + bounce on impact
@keyframes gravity-drop {
0% { transform: translateY(-200px); opacity: 0; }
40% { transform: translateY(0); } 55% { transform: translateY(-30px); }
70% { transform: translateY(0); } 80% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}Squash and stretch with volume conservation
@keyframes rubber-band {
0% { transform: scaleX(1) scaleY(1); }
25% { transform: scaleX(1.3) scaleY(0.8); }
50% { transform: scaleX(0.85) scaleY(1.15); }
75% { transform: scaleX(1.05) scaleY(0.95); }
100% { transform: scaleX(1) scaleY(1); }
}JS: elements accelerate toward cursor past threshold
// Move cursor near the element — it follows
// Uses requestAnimationFrame + spring easingVelocity-preserving scroll with overshoot
// JS: Track scroll velocity, apply deceleration
// curve after release with spring-backCircle morphs to rounded rectangle while expanding
@keyframes morph-expand {
0% { border-radius: 50%; transform: scale(0.5); opacity: 0; }
100% { border-radius: var(--radius-md); transform: scale(1); opacity: 1; }
}Old slides up/fades, new slides up from below
@keyframes state-swap-out { 0% { transform: translateY(0); opacity: 1; } 100% { transform: translateY(-10px); opacity: 0; } }
@keyframes state-swap-in { 0% { transform: translateY(10px); opacity: 0; } 100% { transform: translateY(0); opacity: 1; } }grid-template-rows 0fr to 1fr height animation
.accordion-squish {
display: grid; grid-template-rows: 0fr;
transition: grid-template-rows 240ms var(--ease-smooth);
}
.accordion-squish.accordion-open { grid-template-rows: 1fr; }180deg Y-axis flip with perspective
@keyframes card-flip-front { 0% { transform: perspective(600px) rotateY(0deg); } 100% { transform: perspective(600px) rotateY(180deg); } }
@keyframes card-flip-back { 0% { transform: perspective(600px) rotateY(-180deg); } 100% { transform: perspective(600px) rotateY(0deg); } }Full card compresses to badge/chip
@keyframes collapse-to-chip {
0% { transform: scale(1); border-radius: var(--radius-md); opacity: 1; }
100% { transform: scale(0.4, 0.3); border-radius: var(--radius-full); opacity: 0; }
}Sequenced entries with staggered delays and scroll-driven animations tied to viewport position.
Multiple items entering with sequential delays. Different spring configs produce different feels — snappy, standard, or gentle.
Animations tied to scroll position using animation-timeline: view() — scroll down to trigger them.
Animations composed together in real UI patterns. Toast, modal, form validation.
Spawns toast notifications with slide-slam entrance and fling-right auto-dismiss.
Backdrop fades in, modal enters with stamp-in, closes with press-away.
Empty submit shakes with error-jolt. Valid submit confirms with confirm-thud.