What is the difference between a CSS transition and a CSS animation?
A transition animates a property from its old value to its new one when it changes (on
:hover, a class toggle); an animation runs@keyframeson its own, with any number of steps, can loop, and needs no state change.HTML<style> .btn { background: #2563eb; color: white; border: 0; padding: 10px 16px; border-radius: 6px; transition: background-color 0.3s ease, transform 0.3s ease; } .btn:hover { background: #7c3aed; transform: translateY(-3px); } </style> <button class="btn">Hover me</button>More in transition.
How do you create a CSS animation with @keyframes?
Define the steps with
@keyframes name { ... }, then attach them withanimation: at least a name and a duration.HTML<style> @keyframes spin { to { transform: rotate(360deg); } } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } } .spinner { width: 32px; height: 32px; border: 4px solid #dbeafe; border-top-color: #2563eb; border-radius: 50%; animation: spin 0.8s linear infinite; } .dot { width: 20px; height: 20px; margin-top: 16px; border-radius: 50%; background: crimson; animation: pulse 1.2s ease-in-out infinite; } </style> <div class="spinner"></div> <div class="dot"></div>Useful longhands:
-iteration-count,-direction(alternateplays back and forth),-fill-mode(forwardskeeps the last frame) and-play-state. More in animation.What does the transform property do, and does it affect layout?
transformmoves, rotates, scales or skews an element and does not affect layout: it keeps its original space and its neighbors do not move.HTML<style> .row { display: flex; gap: 10px; } .row div { width: 70px; height: 70px; background: #2563eb; color: white; display: grid; place-items: center; } .t1 { transform: translate(10px, 10px); } .t2 { transform: rotate(20deg); } .t3 { transform: scale(1.3); } </style> <div class="row"><div>none</div><div class="t1">move</div><div class="t2">rotate</div><div class="t3">scale</div></div>Order matters: each function works in the coordinates left by the one before, so
rotate(45deg) translateX(50px)differs from the reverse.transform-originsets the pivot.Why can you not transition display: none, and what do you do instead?
displayis discrete: there is no halfway betweennoneandblock, so the change is instant. Animateopacitywithvisibilityinstead;visibility: hiddenalso keeps the element out of the tab order.HTML<style> .panel { opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity 0.25s, transform 0.25s, visibility 0.25s; background: #dbeafe; padding: 10px; margin-top: 8px; } #toggle:checked ~ .panel { opacity: 1; visibility: visible; transform: none; } </style> <input type="checkbox" id="toggle"> <label for="toggle">Show panel</label> <div class="panel">Fades and slides, and is not focusable while hidden.</div>Chrome and Safari 18 can animate
displaywithtransition-behavior: allow-discreteand@starting-style; Firefox cannot yet.Which CSS properties are cheap to animate, and why?
transformandopacity. In the style, layout, paint, composite pipeline they touch only composite, which can run off the main thread, so they stay smooth.width,top,leftandmarginforce layout every frame;background-colorandbox-shadowforce paint. Move withtranslate(), grow withscale(), and usewill-change: transformsparingly: every layer costs memory.How do you respect users who turn off animations?
Use the
prefers-reduced-motionmedia query, which matches when the user turned on "reduce motion" in their operating system.@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }Cleaner: write static styles first and add motion inside
@media (prefers-reduced-motion: no-preference). A short fade is fine; parallax and large slides should go.What do ease, linear, ease-in-out and steps() mean in CSS animations?
They are timing functions: how the value moves from start to end over the duration.
linearis constant speed,ease(the default) speeds up quickly and slows down at the end,ease-instarts slow,ease-outends slow,ease-in-outdoes both.cubic-bezier()draws your own curve, andsteps(n)jumps in n equal steps with no frames in between, for sprites and typewriter effects.HTML<style> .track { width: 300px; background: #e5e7eb; margin-bottom: 4px; } .ball { width: 24px; height: 24px; border-radius: 50%; background: #2563eb; animation: go 2s infinite alternate; } @keyframes go { to { transform: translateX(276px); } } </style> <div class="track"><div class="ball" style="animation-timing-function: linear"></div></div> <div class="track"><div class="ball" style="animation-timing-function: ease-in-out"></div></div> <div class="track"><div class="ball" style="animation-timing-function: steps(5)"></div></div>How do you replay a CSS animation from JavaScript?
Removing and re-adding the class in the same frame does nothing, because the browser never sees it gone. Remove it, force a style recalculation by reading a layout property such as
offsetWidth, then add it back, and remove it onanimationend.HTML<style> @keyframes shake { 25% { transform: translateX(-8px); } 75% { transform: translateX(8px); } } .shake { animation: shake 0.3s 2; } input { padding: 6px; } </style> <input value="wrong password"> <button>Submit</button> <script> const input = document.querySelector('input'); document.querySelector('button').addEventListener('click', () => { input.classList.remove('shake'); void input.offsetWidth; // forces a reflow, so the removal counts input.classList.add('shake'); }); input.addEventListener('animationend', () => input.classList.remove('shake')); </script>The Web Animations API (
element.animate()) replays an animation without the trick.Why does one transform cancel another, and how do translate, rotate and scale help?
transformis one property, so two rules or animations that set it overwrite each other: a hovertransform: scale(1.3)wipes out atransform: rotate(45deg). The separatetranslate,rotateandscaleproperties (in every major browser since 2022) combine instead, applied in that order beforetransform, and each can be animated on its own.HTML<style> .box { display: inline-block; width: 60px; height: 60px; margin: 30px; background: #2563eb; rotate: 45deg; transition: scale 0.2s; } .box:hover { scale: 1.3; } .old { rotate: none; transform: rotate(45deg); transition: transform 0.2s; } .old:hover { scale: none; transform: scale(1.3); } </style> <div class="box"></div><div class="box old"></div> <p>Hover both: the first keeps its tilt, the second loses it.</p>How do you animate an element's height to auto?
You cannot transition to
height: autodirectly, because the browser cannot interpolate to a keyword. The clean trick is a grid row that goes from0frto1fraround a child withoverflow: hidden. Older code animatesmax-heightto a large number, which gets the timing wrong. Chrome 129+ also acceptsinterpolate-size: allow-keywords, which letsheight: autoanimate.HTML<style> .panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.3s ease; } .panel > div { overflow: hidden; } #open:checked ~ .panel { grid-template-rows: 1fr; } .panel p { margin: 0; padding: 10px; background: #dbeafe; } </style> <input type="checkbox" id="open"> <label for="open">Show details</label> <div class="panel"><div><p>Any amount of text: the row opens to its real height and the transition follows.</p></div></div>
CSS Animation and Transition Interview Questions
Transitions, keyframes, transforms, what is cheap to animate, and reduced motion.
10 questions