SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/transitions

7 CSS Transitions

4 Pure CSS3 with JSPublished 2026-10-05Free · no sign-up

Seven demos that show how CSS transitions behave: easing curves side by side, different timings for each property, a panel that fades in and out of display: none, a smooth theme change, menu items with staggered delays, scaling from different origins and a stepped transition.

01. Easing Curves Side by Side

TransitionEasing

Four dots travel the same distance on hover or focus with linear, ease, ease-in-out and a cubic-bezier that overshoots. Watching them together shows why easing matters: linear looks mechanical, and an overshoot feels springy. The durations are identical.

View code
<div class="tr1" tabindex="0" aria-label="Hover or focus to run the comparison"><p><span>linear</span><i style="--e:linear"></i></p><p><span>ease</span><i style="--e:ease"></i></p><p><span>ease-in-out</span><i style="--e:ease-in-out"></i></p><p><span>back-out</span><i style="--e:cubic-bezier(.34,1.56,.64,1)"></i></p></div>
.tr1 { display: grid; gap: 10px; width: 290px; max-width: 100%; box-sizing: border-box; padding: 14px; border: 1px solid #e4e7ec; border-radius: 14px; background: #fff; font: 600 12px "JetBrains Mono", ui-monospace, monospace; color: #475467; }
.tr1:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
.tr1 p { position: relative; display: flex; align-items: center; height: 26px; margin: 0; border-radius: 13px; background: #f2f4f7; }
.tr1 span { position: absolute; right: 10px; }
.tr1 i { width: 26px; height: 26px; border-radius: 50%; background: #7f56d9; transition: transform 1s var(--e); }
.tr1:hover i, .tr1:focus-visible i { transform: translateX(150px); }
@media (prefers-reduced-motion: reduce) { .tr1 i { transition-duration: .01s; } }

02. Different Timings per Property

TransitionTiming

One transition declaration can give each property its own duration and delay: here the colour changes quickly, the lift is slower and the shadow follows a moment later. Listing properties explicitly also avoids transition: all, which animates things you did not mean to.

View code
<a class="tr2" href="#plans">See plans</a>
.tr2 { display: inline-block; padding: 16px 28px; border-radius: 12px; background: #1e293b; color: #e2e8f0; font: 700 16px Inter, system-ui, sans-serif; text-decoration: none; transition: background-color .15s ease, color .15s ease, transform .4s cubic-bezier(.2,.8,.2,1), box-shadow .4s ease .1s; }
.tr2:hover, .tr2:focus-visible { background: #f59e0b; color: #1c1917; transform: translateY(-4px); box-shadow: 0 14px 24px -12px rgba(245,158,11,.8); }
.tr2:focus-visible { outline: 3px solid #fcd34d; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .tr2 { transition: background-color .15s, color .15s; } .tr2:hover, .tr2:focus-visible { transform: none; } }

03. Animating display: none

Transition@starting-style

Panels shown and hidden with display used to pop in and out. transition-behavior: allow-discrete lets display wait for the fade to finish, and @starting-style gives the entry animation its first frame. In older browsers the panel still opens and closes, just instantly.

View code
<div class="tr3"><button aria-expanded="false" aria-controls="tr3p">Show details</button><div id="tr3p" class="p">Ships in 2-3 working days from our studio in Porto. Free returns within 30 days.</div></div>
.tr3 { width: 270px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.tr3 button { padding: 10px 16px; border: 1px solid #d0d5dd; border-radius: 10px; background: #fff; color: #101828; font: 600 14px Inter, system-ui, sans-serif; cursor: pointer; }
.tr3 button:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
.tr3 .p { display: none; margin-top: 10px; padding: 14px; border-radius: 12px; background: #101828; color: #f2f4f7; font-size: 14px; line-height: 1.5; opacity: 0; transform: translateY(-6px); transition: opacity .3s ease, transform .3s ease, display .3s allow-discrete; }
.tr3 .p.open { display: block; opacity: 1; transform: none; }
@starting-style { .tr3 .p.open { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) { .tr3 .p { transition: none; } }
var b = document.querySelector('.tr3 button'), p = document.getElementById('tr3p');
b.onclick = function () { var o = p.classList.toggle('open'); b.setAttribute('aria-expanded', o); b.textContent = o ? 'Hide details' : 'Show details'; };

04. Smooth Theme Colour Change

TransitionTheme

Switching a card between light and dark transitions its colours over a quarter of a second instead of flashing. The colours are custom properties on the card, and only background-color, color and border-color are transitioned so nothing else is affected.

View code
<div class="tr4"><h3>Weekly summary</h3><p>12 orders · $1,840 revenue</p><button aria-pressed="false">Dark mode</button></div>
.tr4 { --bg: #ffffff; --fg: #101828; --mut: #475467; --bd: #e4e7ec; width: 260px; max-width: 100%; box-sizing: border-box; padding: 20px; border: 1px solid var(--bd); border-radius: 16px; background: var(--bg); color: var(--fg); font-family: Inter, system-ui, sans-serif; transition: background-color .25s ease, color .25s ease, border-color .25s ease; }
.tr4.dk { --bg: #0f172a; --fg: #f8fafc; --mut: #94a3b8; --bd: #334155; }
.tr4 h3 { margin: 0 0 4px; } .tr4 p { margin: 0 0 14px; color: var(--mut); font-size: 14px; transition: color .25s ease; }
.tr4 button { padding: 8px 14px; border: 1px solid var(--bd); border-radius: 8px; background: transparent; color: var(--fg); font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; transition: color .25s ease, border-color .25s ease; }
.tr4 button:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .tr4, .tr4 p, .tr4 button { transition: none; } }
var c = document.querySelector('.tr4'), b = c.querySelector('button');
b.onclick = function () { var on = c.classList.toggle('dk'); b.setAttribute('aria-pressed', on); };

05. Menu Items with Staggered Delays

TransitionDelay

A menu whose items slide in one after another, using transition-delay calculated from each item's index. Closing reverses quickly with no delay, because people expect things to leave faster than they arrive.

View code
<div class="tr5"><button aria-expanded="false" aria-controls="tr5m">Menu</button><ul id="tr5m"><li style="--i:1"><a href="#shop">Shop</a></li><li style="--i:2"><a href="#journal">Journal</a></li><li style="--i:3"><a href="#about">About</a></li><li style="--i:4"><a href="#contact">Contact</a></li></ul></div>
.tr5 { width: 220px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.tr5 button { padding: 10px 16px; border: 0; border-radius: 10px; background: #f472b6; color: #2a0a1a; font: 700 14px Inter, system-ui, sans-serif; cursor: pointer; }
.tr5 button:focus-visible { outline: 3px solid #fbcfe8; outline-offset: 2px; }
.tr5 ul { margin: 10px 0 0; padding: 0; list-style: none; visibility: hidden; transition: visibility 0s .2s; }
.tr5 li { opacity: 0; transform: translateX(-14px); transition: opacity .15s ease, transform .15s ease; }
.tr5 a { display: block; padding: 8px 4px; color: #fce7f3; font-size: 20px; font-weight: 800; text-decoration: none; }
.tr5 a:focus-visible { outline: 2px solid #f9a8d4; outline-offset: 2px; border-radius: 4px; }
.tr5.open ul { visibility: visible; transition: none; }
.tr5.open li { opacity: 1; transform: none; transition: opacity .3s ease, transform .3s ease; transition-delay: calc(var(--i) * 60ms); }
@media (prefers-reduced-motion: reduce) { .tr5 li, .tr5.open li { transition: none; transform: none; } }
var w = document.querySelector('.tr5'), b = w.querySelector('button');
b.onclick = function () { var o = w.classList.toggle('open'); b.setAttribute('aria-expanded', o); };

06. Scaling from Different Origins

Transitiontransform-origin

Three dropdown-style panels scale from a different transform-origin: top left, top centre and top right. A menu should grow out of the button that opened it, and the origin is the one line that makes that happen.

View code
<div class="tr6" tabindex="0" aria-label="Hover or focus to compare origins"><div style="--o:top left">top left</div><div style="--o:top center">top center</div><div style="--o:top right">top right</div></div>
.tr6 { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; width: 330px; max-width: 100%; padding: 6px; box-sizing: border-box; font: 600 12px "JetBrains Mono", ui-monospace, monospace; }
.tr6:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; border-radius: 10px; }
.tr6 div { display: grid; place-items: center; width: 92px; height: 110px; border-radius: 12px; background: #eef4ff; border: 1px solid #c7d7fe; color: #1e3a8a; transform: scale(.4); transform-origin: var(--o); transition: transform .45s cubic-bezier(.2,.8,.2,1); }
.tr6:hover div, .tr6:focus-visible div { transform: none; }
@media (prefers-reduced-motion: reduce) { .tr6 div { transition: none; } }

07. Stepped Transition with steps()

Transitionsteps()

steps(4) turns a smooth transition into four equal jumps, useful for ticking counters, sprite sheets and retro effects. Here a battery goes from one bar to five in discrete jumps on hover or focus instead of a smooth slide.

View code
<div class="tr7" tabindex="0" role="img" aria-label="Battery icon, fills on hover"><span></span></div>
.tr7 { position: relative; width: 140px; height: 64px; padding: 6px; box-sizing: border-box; border: 4px solid #e2e8f0; border-radius: 12px; }
.tr7::after { content: ""; position: absolute; right: -12px; top: 18px; width: 8px; height: 20px; border-radius: 0 4px 4px 0; background: #e2e8f0; }
.tr7 span { display: block; height: 100%; width: 20%; border-radius: 4px; background: repeating-linear-gradient(90deg, #22c55e 0 20%, transparent 20% 21.5%); background-size: 120px 100%; transition: width 1.5s steps(4, end); }
.tr7:hover span, .tr7:focus-visible span { width: 100%; }
.tr7:focus-visible { outline: 3px solid #86efac; outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .tr7 span { transition: none; } }

About these CSS Transitions

List the properties

transition: all animates every property that changes, including ones you did not intend, such as width during a resize. Naming the properties (transform .3s, opacity .3s) is clearer and cheaper.

Easing sets the personality

ease-out feels responsive for things entering, ease-in suits things leaving, and a cubic-bezier() with a value above 1 overshoots for a springy feel. Linear motion looks mechanical except for loaders and progress.

Transitioning to and from display: none

Add transition-behavior: allow-discrete (or allow-discrete in the shorthand) so display flips at the right moment, and set the entry starting point in @starting-style.

Faster out than in

Elements that appear can take 250 to 400ms; elements that leave should be quicker, because the person has already decided to move on. Use a delay of zero on the way out.

Frequently asked questions

What is the difference between a transition and an animation?

A transition runs between two states when a property changes; an animation runs keyframes on its own, and can loop.

Why does my transition not run on page load?

Transitions need a change. Use @starting-style or a keyframe animation for the first appearance.

How do I respect reduced motion?

Each demo shortens or removes its movement inside a prefers-reduced-motion media query, keeping colour changes.